mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
can get exact answers
This commit is contained in:
@@ -17,6 +17,7 @@ export function makeQueryClient() {
|
|||||||
// refetchOnMount: false,
|
// refetchOnMount: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
retry: 0,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const message = getAxiosErrorMessage(error as AxiosError);
|
const message = getAxiosErrorMessage(error as AxiosError);
|
||||||
console.error("Mutation error:", message);
|
console.error("Mutation error:", message);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
rateLimitAwarePost,
|
rateLimitAwarePost,
|
||||||
} from "./canvasWebRequestUtils";
|
} from "./canvasWebRequestUtils";
|
||||||
|
|
||||||
export const getAnswers = (
|
export const getAnswersForCanvas = (
|
||||||
question: LocalQuizQuestion,
|
question: LocalQuizQuestion,
|
||||||
settings: LocalCourseSettings
|
settings: LocalCourseSettings
|
||||||
) => {
|
) => {
|
||||||
@@ -32,6 +32,13 @@ export const getAnswers = (
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (question.questionType === QuestionType.NUMERICAL) {
|
||||||
|
return question.answers.map((answer) => ({
|
||||||
|
numerical_answer_type: answer.numericalAnswerType,
|
||||||
|
exact: answer.numericAnswer,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return question.answers.map((answer) => ({
|
return question.answers.map((answer) => ({
|
||||||
answer_html: markdownToHTMLSafe({ markdownString: answer.text, settings }),
|
answer_html: markdownToHTMLSafe({ markdownString: answer.text, settings }),
|
||||||
answer_weight: answer.correct ? 100 : 0,
|
answer_weight: answer.correct ? 100 : 0,
|
||||||
@@ -64,7 +71,7 @@ const createQuestionOnly = async (
|
|||||||
question_type: getQuestionTypeForCanvas(question),
|
question_type: getQuestionTypeForCanvas(question),
|
||||||
points_possible: question.points,
|
points_possible: question.points,
|
||||||
position,
|
position,
|
||||||
answers: getAnswers(question, settings),
|
answers: getAnswersForCanvas(question, settings),
|
||||||
correct_comments: question.incorrectComments,
|
correct_comments: question.incorrectComments,
|
||||||
incorrect_comments: question.incorrectComments,
|
incorrect_comments: question.incorrectComments,
|
||||||
neutral_comments: question.neutralComments,
|
neutral_comments: question.neutralComments,
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import { getCoursePathByName } from "../globalSettings/globalSettingsFileStorage
|
|||||||
import {
|
import {
|
||||||
localPageMarkdownUtils,
|
localPageMarkdownUtils,
|
||||||
} from "@/features/local/pages/localCoursePageModels";
|
} from "@/features/local/pages/localCoursePageModels";
|
||||||
import {
|
import { quizMarkdownUtils } from "../quizzes/models/utils/quizMarkdownUtils";
|
||||||
localQuizMarkdownUtils,
|
|
||||||
} from "@/features/local/quizzes/models/localQuiz";
|
|
||||||
|
|
||||||
const getItemFileNames = async ({
|
const getItemFileNames = async ({
|
||||||
courseName,
|
courseName,
|
||||||
@@ -61,7 +60,7 @@ const getItem = async <T extends CourseItemType>({
|
|||||||
name
|
name
|
||||||
) as CourseItemReturnType<T>;
|
) as CourseItemReturnType<T>;
|
||||||
} else if (type === "Quiz") {
|
} else if (type === "Quiz") {
|
||||||
return localQuizMarkdownUtils.parseMarkdown(
|
return quizMarkdownUtils.parseMarkdown(
|
||||||
rawFile,
|
rawFile,
|
||||||
name
|
name
|
||||||
) as CourseItemReturnType<T>;
|
) as CourseItemReturnType<T>;
|
||||||
|
|||||||
@@ -25,4 +25,26 @@ What is 2+3?
|
|||||||
expect(question.questionType).toBe(QuestionType.NUMERICAL);
|
expect(question.questionType).toBe(QuestionType.NUMERICAL);
|
||||||
expect(question.answers[0].numericAnswer).toBe(5);
|
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
|
||||||
|
// `;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -201,6 +201,38 @@ describe("QuizDeterministicChecks", () => {
|
|||||||
const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz);
|
const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz);
|
||||||
const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown, name);
|
const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown, name);
|
||||||
|
|
||||||
|
expect(parsedQuiz).toEqual(quiz);
|
||||||
|
});
|
||||||
|
it("SerializationIsDeterministic Numeric with exact 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: "= 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);
|
expect(parsedQuiz).toEqual(quiz);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
getQuestionTypeForCanvas,
|
getQuestionTypeForCanvas,
|
||||||
getAnswers,
|
getAnswersForCanvas,
|
||||||
} from "@/features/canvas/services/canvasQuizService";
|
} from "@/features/canvas/services/canvasQuizService";
|
||||||
import {
|
import {
|
||||||
QuestionType,
|
QuestionType,
|
||||||
zodQuestionType,
|
|
||||||
} from "@/features/local/quizzes/models/localQuizQuestion";
|
} from "@/features/local/quizzes/models/localQuizQuestion";
|
||||||
import { quizMarkdownUtils } from "@/features/local/quizzes/models/utils/quizMarkdownUtils";
|
import { quizMarkdownUtils } from "@/features/local/quizzes/models/utils/quizMarkdownUtils";
|
||||||
import { quizQuestionMarkdownUtils } from "@/features/local/quizzes/models/utils/quizQuestionMarkdownUtils";
|
import { quizQuestionMarkdownUtils } from "@/features/local/quizzes/models/utils/quizQuestionMarkdownUtils";
|
||||||
@@ -255,7 +254,7 @@ short_answer=
|
|||||||
|
|
||||||
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name);
|
||||||
const firstQuestion = quiz.questions[0];
|
const firstQuestion = quiz.questions[0];
|
||||||
const answers = getAnswers(firstQuestion, {
|
const answers = getAnswersForCanvas(firstQuestion, {
|
||||||
name: "",
|
name: "",
|
||||||
assignmentGroups: [],
|
assignmentGroups: [],
|
||||||
daysOfWeek: [],
|
daysOfWeek: [],
|
||||||
|
|||||||
@@ -1,22 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { LocalQuizQuestion, zodLocalQuizQuestion } from "./localQuizQuestion";
|
import { zodLocalQuizQuestion } from "./localQuizQuestion";
|
||||||
import { quizMarkdownUtils } from "./utils/quizMarkdownUtils";
|
|
||||||
import { IModuleItem } from "@/features/local/modules/IModuleItem";
|
import { IModuleItem } from "@/features/local/modules/IModuleItem";
|
||||||
|
|
||||||
export interface LocalQuiz extends IModuleItem {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
password?: string;
|
|
||||||
lockAt?: string; // ISO 8601 date string
|
|
||||||
dueAt: string; // ISO 8601 date string
|
|
||||||
shuffleAnswers: boolean;
|
|
||||||
showCorrectAnswers: boolean;
|
|
||||||
oneQuestionAtATime: boolean;
|
|
||||||
localAssignmentGroupName?: string;
|
|
||||||
allowedAttempts: number;
|
|
||||||
questions: LocalQuizQuestion[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const zodLocalQuiz = z.object({
|
export const zodLocalQuiz = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
@@ -31,7 +16,4 @@ export const zodLocalQuiz = z.object({
|
|||||||
questions: zodLocalQuizQuestion.array(),
|
questions: zodLocalQuizQuestion.array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const localQuizMarkdownUtils = {
|
export interface LocalQuiz extends IModuleItem, z.infer<typeof zodLocalQuiz> {}
|
||||||
parseMarkdown: quizMarkdownUtils.parseMarkdown,
|
|
||||||
toMarkdown: quizMarkdownUtils.toMarkdown,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { zodLocalQuizQuestionAnswer } from "./localQuizQuestionAnswer";
|
||||||
LocalQuizQuestionAnswer,
|
|
||||||
zodLocalQuizQuestionAnswer,
|
|
||||||
} from "./localQuizQuestionAnswer";
|
|
||||||
|
|
||||||
export enum QuestionType {
|
|
||||||
MULTIPLE_ANSWERS = "multiple_answers",
|
|
||||||
MULTIPLE_CHOICE = "multiple_choice",
|
|
||||||
ESSAY = "essay",
|
|
||||||
SHORT_ANSWER = "short_answer",
|
|
||||||
MATCHING = "matching",
|
|
||||||
NONE = "",
|
|
||||||
SHORT_ANSWER_WITH_ANSWERS = "short_answer=",
|
|
||||||
NUMERICAL = "numerical",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const zodQuestionType = z.enum([
|
export const zodQuestionType = z.enum([
|
||||||
QuestionType.MULTIPLE_ANSWERS,
|
"multiple_answers",
|
||||||
QuestionType.MULTIPLE_CHOICE,
|
"multiple_choice",
|
||||||
QuestionType.ESSAY,
|
"essay",
|
||||||
QuestionType.SHORT_ANSWER,
|
"short_answer",
|
||||||
QuestionType.MATCHING,
|
"matching",
|
||||||
QuestionType.NONE,
|
"",
|
||||||
QuestionType.SHORT_ANSWER_WITH_ANSWERS,
|
"short_answer=",
|
||||||
|
"numerical",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export interface LocalQuizQuestion {
|
export const QuestionType = {
|
||||||
text: string;
|
MULTIPLE_ANSWERS: "multiple_answers",
|
||||||
questionType: QuestionType;
|
MULTIPLE_CHOICE: "multiple_choice",
|
||||||
points: number;
|
ESSAY: "essay",
|
||||||
answers: LocalQuizQuestionAnswer[];
|
SHORT_ANSWER: "short_answer",
|
||||||
matchDistractors: string[];
|
MATCHING: "matching",
|
||||||
correctComments?: string;
|
NONE: "",
|
||||||
incorrectComments?: string;
|
SHORT_ANSWER_WITH_ANSWERS: "short_answer=",
|
||||||
neutralComments?: string;
|
NUMERICAL: "numerical",
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
|
export type QuestionType = z.infer<typeof zodQuestionType>;
|
||||||
|
|
||||||
export const zodLocalQuizQuestion = z.object({
|
export const zodLocalQuizQuestion = z.object({
|
||||||
text: z.string(),
|
text: z.string(),
|
||||||
questionType: zodQuestionType,
|
questionType: zodQuestionType,
|
||||||
@@ -45,3 +35,4 @@ export const zodLocalQuizQuestion = z.object({
|
|||||||
incorrectComments: z.string().optional(),
|
incorrectComments: z.string().optional(),
|
||||||
neutralComments: z.string().optional(),
|
neutralComments: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
export type LocalQuizQuestion = z.infer<typeof zodLocalQuizQuestion>;
|
||||||
|
|||||||
@@ -1,36 +1,3 @@
|
|||||||
type FeedbackType = "+" | "-" | "...";
|
|
||||||
|
|
||||||
const extractFeedbackContent = (
|
|
||||||
trimmedLine: string,
|
|
||||||
feedbackType: FeedbackType
|
|
||||||
): string => {
|
|
||||||
if (trimmedLine === feedbackType) return "";
|
|
||||||
|
|
||||||
const prefixLength = feedbackType === "..." ? 4 : 2; // "... " is 4 chars, "+ " and "- " are 2
|
|
||||||
return trimmedLine.substring(prefixLength);
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveFeedback = (
|
|
||||||
feedbackType: FeedbackType | null,
|
|
||||||
feedbackLines: string[],
|
|
||||||
comments: {
|
|
||||||
correct?: string;
|
|
||||||
incorrect?: string;
|
|
||||||
neutral?: string;
|
|
||||||
}
|
|
||||||
): void => {
|
|
||||||
if (!feedbackType || feedbackLines.length === 0) return;
|
|
||||||
|
|
||||||
const feedbackText = feedbackLines.join("\n");
|
|
||||||
if (feedbackType === "+") {
|
|
||||||
comments.correct = feedbackText;
|
|
||||||
} else if (feedbackType === "-") {
|
|
||||||
comments.incorrect = feedbackText;
|
|
||||||
} else if (feedbackType === "...") {
|
|
||||||
comments.neutral = feedbackText;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type feedbackTypeOptions = "correct" | "incorrect" | "neutral" | "none";
|
type feedbackTypeOptions = "correct" | "incorrect" | "neutral" | "none";
|
||||||
|
|
||||||
export const quizFeedbackMarkdownUtils = {
|
export const quizFeedbackMarkdownUtils = {
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ export const quizQuestionAnswerMarkdownUtils = {
|
|||||||
return `${questionTypeIndicator}${multilineMarkdownCompatibleText}`;
|
return `${questionTypeIndicator}${multilineMarkdownCompatibleText}`;
|
||||||
} else if (question.questionType === "matching") {
|
} else if (question.questionType === "matching") {
|
||||||
return `^ ${answer.text} - ${answer.matchedText}`;
|
return `^ ${answer.text} - ${answer.matchedText}`;
|
||||||
|
} else if (question.questionType === "numerical") {
|
||||||
|
return `= ${answer.numericAnswer}`;
|
||||||
} else {
|
} else {
|
||||||
const questionLetter = String.fromCharCode(97 + index);
|
const questionLetter = String.fromCharCode(97 + index);
|
||||||
const correctIndicator = answer.correct ? "*" : "";
|
const correctIndicator = answer.correct ? "*" : "";
|
||||||
|
|||||||
Reference in New Issue
Block a user