Prevented the bug where the application is crashing

This commit is contained in:
Tiago Ribeiro
2024-03-24 02:32:12 +00:00
parent 29b2c8b3b8
commit 22f2b43692
8 changed files with 86 additions and 68 deletions

View File

@@ -38,7 +38,7 @@ export default function Level({exam, showSolutions = false, onFinish}: Props) {
const nextExercise = (solution?: UserSolution) => { const nextExercise = (solution?: UserSolution) => {
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "level", exam: exam.id}]);
} }
setQuestionIndex((prev) => prev + currentQuestionIndex); setQuestionIndex((prev) => prev + currentQuestionIndex);
@@ -52,17 +52,15 @@ export default function Level({exam, showSolutions = false, onFinish}: Props) {
setHasExamEnded(false); setHasExamEnded(false);
if (solution) { if (solution) {
onFinish( onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "level", exam: exam.id}]);
[...userSolutions.filter((x) => x.exercise !== solution.exercise), solution].map((x) => ({...x, module: "level", exam: exam.id})),
);
} else { } else {
onFinish(userSolutions.map((x) => ({...x, module: "level", exam: exam.id}))); onFinish(userSolutions);
} }
}; };
const previousExercise = (solution?: UserSolution) => { const previousExercise = (solution?: UserSolution) => {
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "level", exam: exam.id}]);
} }
if (exerciseIndex > 0) { if (exerciseIndex > 0) {

View File

@@ -55,13 +55,13 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
return; return;
} }
onFinish(userSolutions.map((x) => ({...x, module: "listening", exam: exam.id}))); onFinish(userSolutions);
}; };
const nextExercise = (solution?: UserSolution) => { const nextExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "listening", exam: exam.id}]);
} }
setQuestionIndex((prev) => prev + currentQuestionIndex); setQuestionIndex((prev) => prev + currentQuestionIndex);
@@ -91,18 +91,16 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
setHasExamEnded(false); setHasExamEnded(false);
if (solution) { if (solution) {
onFinish( onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "listening", exam: exam.id}]);
[...userSolutions.filter((x) => x.exercise !== solution.exercise), solution].map((x) => ({...x, module: "listening", exam: exam.id})),
);
} else { } else {
onFinish(userSolutions.map((x) => ({...x, module: "listening", exam: exam.id}))); onFinish(userSolutions);
} }
}; };
const previousExercise = (solution?: UserSolution) => { const previousExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "listening", exam: exam.id}]);
} }
setExerciseIndex(exerciseIndex - 1); setExerciseIndex(exerciseIndex - 1);

View File

@@ -128,13 +128,13 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
return; return;
} }
onFinish(userSolutions.map((x) => ({...x, module: "reading", exam: exam.id}))); onFinish(userSolutions);
}; };
const nextExercise = (solution?: UserSolution) => { const nextExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "reading", exam: exam.id}]);
} }
setQuestionIndex((prev) => prev + currentQuestionIndex); setQuestionIndex((prev) => prev + currentQuestionIndex);
setStoreQuestionIndex(0); setStoreQuestionIndex(0);
@@ -165,18 +165,16 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
setHasExamEnded(false); setHasExamEnded(false);
if (solution) { if (solution) {
onFinish( onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "reading", exam: exam.id}]);
[...userSolutions.filter((x) => x.exercise !== solution.exercise), solution].map((x) => ({...x, module: "reading", exam: exam.id})),
);
} else { } else {
onFinish(userSolutions.map((x) => ({...x, module: "reading", exam: exam.id}))); onFinish(userSolutions);
} }
}; };
const previousExercise = (solution?: UserSolution) => { const previousExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "reading", exam: exam.id}]);
} }
setStoreQuestionIndex(0); setStoreQuestionIndex(0);

View File

@@ -36,7 +36,7 @@ export default function Speaking({exam, showSolutions = false, onFinish}: Props)
const nextExercise = (solution?: UserSolution) => { const nextExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "speaking", exam: exam.id}]);
} }
setQuestionIndex((prev) => prev + currentQuestionIndex); setQuestionIndex((prev) => prev + currentQuestionIndex);
@@ -50,18 +50,16 @@ export default function Speaking({exam, showSolutions = false, onFinish}: Props)
setHasExamEnded(false); setHasExamEnded(false);
if (solution) { if (solution) {
onFinish( onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "speaking", exam: exam.id}]);
[...userSolutions.filter((x) => x.exercise !== solution.exercise), solution].map((x) => ({...x, module: "speaking", exam: exam.id})),
);
} else { } else {
onFinish(userSolutions.map((x) => ({...x, module: "speaking", exam: exam.id}))); onFinish(userSolutions);
} }
}; };
const previousExercise = (solution?: UserSolution) => { const previousExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "speaking", exam: exam.id}]);
} }
if (exerciseIndex > 0) { if (exerciseIndex > 0) {

View File

@@ -28,7 +28,7 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
const nextExercise = (solution?: UserSolution) => { const nextExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "writing", exam: exam.id}]);
} }
if (exerciseIndex + 1 < exam.exercises.length) { if (exerciseIndex + 1 < exam.exercises.length) {
@@ -41,18 +41,16 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
setHasExamEnded(false); setHasExamEnded(false);
if (solution) { if (solution) {
onFinish( onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "writing", exam: exam.id}]);
[...userSolutions.filter((x) => x.exercise !== solution.exercise), solution].map((x) => ({...x, module: "writing", exam: exam.id})),
);
} else { } else {
onFinish(userSolutions.map((x) => ({...x, module: "writing", exam: exam.id}))); onFinish(userSolutions);
} }
}; };
const previousExercise = (solution?: UserSolution) => { const previousExercise = (solution?: UserSolution) => {
scrollToTop(); scrollToTop();
if (solution) { if (solution) {
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), solution]); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "writing", exam: exam.id}]);
} }
if (exerciseIndex > 0) { if (exerciseIndex > 0) {

View File

@@ -41,6 +41,8 @@ export default function ExamPage({page}: Props) {
const assignment = useExamStore((state) => state.assignment); const assignment = useExamStore((state) => state.assignment);
const initialTimeSpent = useExamStore((state) => state.timeSpent); const initialTimeSpent = useExamStore((state) => state.timeSpent);
const examStore = useExamStore;
const {exam, setExam} = useExamStore((state) => state); const {exam, setExam} = useExamStore((state) => state);
const {exams, setExams} = useExamStore((state) => state); const {exams, setExams} = useExamStore((state) => state);
const {sessionId, setSessionId} = useExamStore((state) => state); const {sessionId, setSessionId} = useExamStore((state) => state);
@@ -189,8 +191,8 @@ export default function ExamPage({page}: Props) {
id: solution.id || uuidv4(), id: solution.id || uuidv4(),
timeSpent, timeSpent,
session: sessionId, session: sessionId,
exam: exam!.id, exam: solution.exam!,
module: exam!.module, module: solution.module!,
user: user?.id || "", user: user?.id || "",
date: new Date().getTime(), date: new Date().getTime(),
isDisabled: solution.isDisabled, isDisabled: solution.isDisabled,
@@ -218,6 +220,7 @@ export default function ExamPage({page}: Props) {
const checkIfStatsHaveBeenEvaluated = (ids: string[]) => { const checkIfStatsHaveBeenEvaluated = (ids: string[]) => {
setTimeout(async () => { setTimeout(async () => {
try {
const awaitedStats = await Promise.all(ids.map(async (id) => (await axios.get<Stat>(`/api/stats/${id}`)).data)); const awaitedStats = await Promise.all(ids.map(async (id) => (await axios.get<Stat>(`/api/stats/${id}`)).data));
const solutionsEvaluated = awaitedStats.every((stat) => stat.solutions.every((x) => x.evaluation !== null)); const solutionsEvaluated = awaitedStats.every((stat) => stat.solutions.every((x) => x.evaluation !== null));
if (solutionsEvaluated) { if (solutionsEvaluated) {
@@ -241,6 +244,9 @@ export default function ExamPage({page}: Props) {
} }
return checkIfStatsHaveBeenEvaluated(ids); return checkIfStatsHaveBeenEvaluated(ids);
} catch {
return checkIfStatsHaveBeenEvaluated(ids);
}
}, 5 * 1000); }, 5 * 1000);
}; };
@@ -276,7 +282,7 @@ export default function ExamPage({page}: Props) {
setHasBeenUploaded(true); setHasBeenUploaded(true);
setIsEvaluationLoading(true); setIsEvaluationLoading(true);
await Promise.all( Promise.all(
exam.exercises.map(async (exercise) => { exam.exercises.map(async (exercise) => {
const evaluationID = uuidv4(); const evaluationID = uuidv4();
if (exercise.type === "writing") if (exercise.type === "writing")
@@ -287,8 +293,21 @@ export default function ExamPage({page}: Props) {
}), }),
) )
.then((responses) => { .then((responses) => {
examStore.setState((prev) => {
return {
userSolutions: [
...prev.userSolutions.filter(
(x) =>
!responses
.filter((r) => !!r)
.map((r) => r!.id)
.includes(x.id),
),
...responses.filter((x) => !!x),
] as any,
};
});
setStatsAwaitingEvaluation((prev) => [...prev, ...responses.filter((x) => !!x).map((r) => (r as any).id)]); setStatsAwaitingEvaluation((prev) => [...prev, ...responses.filter((x) => !!x).map((r) => (r as any).id)]);
setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...responses.filter((x) => !!x)] as any);
}) })
.finally(() => { .finally(() => {
setHasBeenUploaded(false); setHasBeenUploaded(false);
@@ -297,7 +316,6 @@ export default function ExamPage({page}: Props) {
axios.get("/api/stats/update"); axios.get("/api/stats/update");
if (exam && exam.module !== "writing" && exam.module !== "speaking")
setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...solutions]); setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...solutions]);
setModuleIndex(moduleIndex + 1); setModuleIndex(moduleIndex + 1);
@@ -306,7 +324,7 @@ export default function ExamPage({page}: Props) {
setQuestionIndex(0); setQuestionIndex(0);
}; };
const aggregateScoresByModule = (answers: UserSolution[]): {module: Module; total: number; missing: number; correct: number}[] => { const aggregateScoresByModule = (): {module: Module; total: number; missing: number; correct: number}[] => {
const scores: { const scores: {
[key in Module]: {total: number; missing: number; correct: number}; [key in Module]: {total: number; missing: number; correct: number};
} = { } = {
@@ -337,7 +355,7 @@ export default function ExamPage({page}: Props) {
}, },
}; };
answers.forEach((x) => { userSolutions.forEach((x) => {
const examModule = const examModule =
x.module || (x.type === "writing" ? "writing" : x.type === "speaking" || x.type === "interactiveSpeaking" ? "speaking" : undefined); x.module || (x.type === "writing" ? "writing" : x.type === "speaking" || x.type === "interactiveSpeaking" ? "speaking" : undefined);
@@ -383,7 +401,7 @@ export default function ExamPage({page}: Props) {
setPartIndex(exams[0].module === "listening" ? -1 : 0); setPartIndex(exams[0].module === "listening" ? -1 : 0);
setExam(exams[0]); setExam(exams[0]);
}} }}
scores={aggregateScoresByModule(userSolutions)} scores={aggregateScoresByModule()}
/> />
); );
} }

View File

@@ -18,6 +18,7 @@ async function GET(req: NextApiRequest, res: NextApiResponse) {
const {id} = req.query; const {id} = req.query;
const snapshot = await getDoc(doc(db, "stats", id as string)); const snapshot = await getDoc(doc(db, "stats", id as string));
if (!snapshot.exists()) return res.status(404).json({id: snapshot.id});
res.status(200).json({...snapshot.data(), id: snapshot.id}); res.status(200).json({...snapshot.data(), id: snapshot.id});
} }

View File

@@ -11,7 +11,7 @@ import {
import axios from "axios"; import axios from "axios";
import {speakingReverseMarking, writingReverseMarking} from "./score"; import {speakingReverseMarking, writingReverseMarking} from "./score";
export const evaluateWritingAnswer = async (exercise: WritingExercise, solution: UserSolution, id: string): Promise<object | undefined> => { export const evaluateWritingAnswer = async (exercise: WritingExercise, solution: UserSolution, id: string): Promise<UserSolution | undefined> => {
const response = await axios.post<Evaluation>("/api/evaluate/writing", { const response = await axios.post<Evaluation>("/api/evaluate/writing", {
question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""), question: `${exercise.prompt} ${exercise.attachment ? exercise.attachment.description : ""}`.replaceAll("\n", ""),
answer: solution.solutions[0].solution.trim().replaceAll("\n", " "), answer: solution.solutions[0].solution.trim().replaceAll("\n", " "),
@@ -35,12 +35,16 @@ export const evaluateWritingAnswer = async (exercise: WritingExercise, solution:
return undefined; return undefined;
}; };
export const evaluateSpeakingAnswer = async (exercise: SpeakingExercise | InteractiveSpeakingExercise, solution: UserSolution, id: string) => { export const evaluateSpeakingAnswer = async (
exercise: SpeakingExercise | InteractiveSpeakingExercise,
solution: UserSolution,
id: string,
): Promise<UserSolution | undefined> => {
switch (exercise?.type) { switch (exercise?.type) {
case "speaking": case "speaking":
return {...(await evaluateSpeakingExercise(exercise, exercise.id, solution, id)), id}; return {...(await evaluateSpeakingExercise(exercise, exercise.id, solution, id)), id} as UserSolution;
case "interactiveSpeaking": case "interactiveSpeaking":
return {...(await evaluateInteractiveSpeakingExercise(exercise.id, solution, id)), id}; return {...(await evaluateInteractiveSpeakingExercise(exercise.id, solution, id)), id} as UserSolution;
default: default:
return undefined; return undefined;
} }
@@ -51,7 +55,12 @@ export const downloadBlob = async (url: string): Promise<Buffer> => {
return Buffer.from(blobResponse.data, "binary"); return Buffer.from(blobResponse.data, "binary");
}; };
const evaluateSpeakingExercise = async (exercise: SpeakingExercise, exerciseId: string, solution: UserSolution, id: string) => { const evaluateSpeakingExercise = async (
exercise: SpeakingExercise,
exerciseId: string,
solution: UserSolution,
id: string,
): Promise<UserSolution | undefined> => {
const formData = new FormData(); const formData = new FormData();
const url = solution.solutions[0].solution.trim() as string; const url = solution.solutions[0].solution.trim() as string;
@@ -92,7 +101,7 @@ const evaluateSpeakingExercise = async (exercise: SpeakingExercise, exerciseId:
return undefined; return undefined;
}; };
const evaluateInteractiveSpeakingExercise = async (exerciseId: string, solution: UserSolution, id: string) => { const evaluateInteractiveSpeakingExercise = async (exerciseId: string, solution: UserSolution, id: string): Promise<UserSolution | undefined> => {
const promiseParts = solution.solutions.map(async (x: {prompt: string; blob: string}) => { const promiseParts = solution.solutions.map(async (x: {prompt: string; blob: string}) => {
const blob = await downloadBlob(x.blob); const blob = await downloadBlob(x.blob);
if (!x.blob.startsWith("blob")) await axios.post("/api/storage/delete", {path: x.blob}); if (!x.blob.startsWith("blob")) await axios.post("/api/storage/delete", {path: x.blob});