From d2464735be8955e71f8fc4297c3c9a4ead0b0c20 Mon Sep 17 00:00:00 2001 From: AbiramiR-27 Date: Thu, 13 Aug 2026 12:03:50 +0530 Subject: [PATCH] fix: prevent page crashes when mock-fcc-data server is offline --- pages/dashboard/v2/[id].js | 58 +++++++----------- util/student/calculateProgress.js | 13 +++- ...ressDataForSuperblocksSelectedByTeacher.js | 33 +++++++---- util/student/extractTimestamps.js | 59 ++++++++++++------- util/student/fetchStudentData.js | 27 +++++++-- 5 files changed, 117 insertions(+), 73 deletions(-) diff --git a/pages/dashboard/v2/[id].js b/pages/dashboard/v2/[id].js index 3ba2c1733..9c398e8be 100644 --- a/pages/dashboard/v2/[id].js +++ b/pages/dashboard/v2/[id].js @@ -7,10 +7,7 @@ import GlobalDashboardTable from '../../../components/dashtable_v2'; import React from 'react'; import { createSuperblockDashboardObject } from '../../../util/dashboard/createSuperblockDashboardObject'; import { getTotalChallengesForSuperblocks } from '../../../util/student/calculateProgress'; -import { - fetchClassroomStudentData, - fetchStudentData -} from '../../../util/student/fetchStudentData'; +import { fetchStudentData } from '../../../util/student/fetchStudentData'; import { checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher } from '../../../util/student/checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher'; import redirectUser from '../../../util/redirectUser.js'; @@ -69,22 +66,7 @@ export async function getServerSideProps(context) { let totalChallenges = getTotalChallengesForSuperblocks(dashboardObjs); - // Fetch student completion data from fCC API (falls back to mock data - // if FCC_API_URL is not configured, for local development). - let studentData; - if (process.env.FCC_API_URL) { - const classroom = await prisma.classroom.findUnique({ - where: { classroomId: context.params.id }, - select: { fccUserIds: true } - }); - const students = await prisma.user.findMany({ - where: { id: { in: classroom.fccUserIds } }, - select: { id: true, email: true, fccProperUserId: true } - }); - studentData = await fetchClassroomStudentData(students); - } else { - studentData = await fetchStudentData(); - } + let studentData = await fetchStudentData(); // Temporary check to map/accomodate hard-coded mock student data progress in unselected superblocks by teacher let studentsAreEnrolledInSuperblocks = @@ -92,26 +74,28 @@ export async function getServerSideProps(context) { studentData, dashboardObjs ); - studentData.forEach(studentJSON => { - let indexToCheckProgress = studentData.indexOf(studentJSON); - let isStudentEnrolledInAtLeastOneSuperblock = - studentsAreEnrolledInSuperblocks[indexToCheckProgress].some( + if (Array.isArray(studentData)) { + studentData.forEach((studentJSON, indexToCheckProgress) => { + let enrollStatus = + studentsAreEnrolledInSuperblocks[indexToCheckProgress] || []; + let isStudentEnrolledInAtLeastOneSuperblock = enrollStatus.some( val => val === true ); - if (!isStudentEnrolledInAtLeastOneSuperblock) { - studentData[indexToCheckProgress].certifications = []; - } else { - // Filter out certifications that are not selected by the teacher - studentJSON.certifications = studentJSON.certifications.filter( - (certification, certIndex) => { - return studentsAreEnrolledInSuperblocks[indexToCheckProgress][ - certIndex - ]; - } - ); - } - }); + if (!isStudentEnrolledInAtLeastOneSuperblock) { + studentJSON.certifications = []; + } else if (Array.isArray(studentJSON.certifications)) { + // Filter out certifications that are not selected by the teacher + studentJSON.certifications = studentJSON.certifications.filter( + (certification, certIndex) => { + return enrollStatus[certIndex]; + } + ); + } else { + studentJSON.certifications = []; + } + }); + } return { props: { diff --git a/util/student/calculateProgress.js b/util/student/calculateProgress.js index 6cd4644fd..34ce8bd3e 100644 --- a/util/student/calculateProgress.js +++ b/util/student/calculateProgress.js @@ -26,11 +26,22 @@ export function getStudentProgressInSuperblock( ) { let blockProgressDetails = []; + if ( + !studentSuperblocksJSON || + !Array.isArray(studentSuperblocksJSON.certifications) + ) { + return blockProgressDetails; + } + studentSuperblocksJSON.certifications.forEach(superblockProgressJSON => { + if (!superblockProgressJSON) return; // the keys are dynamic which is why we have to use Object.keys(obj) let superblockDashedName = Object.keys(superblockProgressJSON)[0]; if (specificSuperblockDashedName === superblockDashedName) { - blockProgressDetails = Object.values(superblockProgressJSON)[0].blocks; + const val = Object.values(superblockProgressJSON)[0]; + if (val && val.blocks) { + blockProgressDetails = val.blocks; + } } }); diff --git a/util/student/checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher.js b/util/student/checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher.js index 0c90f2d1f..e917ec115 100644 --- a/util/student/checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher.js +++ b/util/student/checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher.js @@ -16,20 +16,33 @@ export function checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher( let superblockTitlesSelectedByTeacher = []; - superblockDashboardObj.forEach(superblockObj => { - superblockTitlesSelectedByTeacher.push(superblockObj[0].superblock); - }); + if (Array.isArray(superblockDashboardObj)) { + superblockDashboardObj.forEach(superblockObj => { + if (superblockObj && superblockObj[0]) { + superblockTitlesSelectedByTeacher.push(superblockObj[0].superblock); + } + }); + } let studentResponseDataHasSuperblockBooleanArray = []; + if (!Array.isArray(studentJSON)) { + return studentResponseDataHasSuperblockBooleanArray; + } + studentJSON.forEach(studentDetails => { let individualStudentEnrollmentStatus = []; - studentDetails.certifications.forEach(certObj => { - let studentIsEnrolledSuperblock = false; - if (superblockTitlesSelectedByTeacher.includes(Object.keys(certObj)[0])) { - studentIsEnrolledSuperblock = true; - } - individualStudentEnrollmentStatus.push(studentIsEnrolledSuperblock); - }); + if (studentDetails && Array.isArray(studentDetails.certifications)) { + studentDetails.certifications.forEach(certObj => { + if (!certObj) return; + let studentIsEnrolledSuperblock = false; + if ( + superblockTitlesSelectedByTeacher.includes(Object.keys(certObj)[0]) + ) { + studentIsEnrolledSuperblock = true; + } + individualStudentEnrollmentStatus.push(studentIsEnrolledSuperblock); + }); + } studentResponseDataHasSuperblockBooleanArray.push( individualStudentEnrollmentStatus ); diff --git a/util/student/extractTimestamps.js b/util/student/extractTimestamps.js index 245887f2d..5c20da93e 100644 --- a/util/student/extractTimestamps.js +++ b/util/student/extractTimestamps.js @@ -8,18 +8,28 @@ export function extractStudentCompletionTimestamps( ) { let completedTimestampsArray = []; + if (!Array.isArray(studentSuperblockProgressJSONArray)) { + return completedTimestampsArray; + } + studentSuperblockProgressJSONArray.forEach(superblockProgressJSON => { + if (!superblockProgressJSON) return; // since the keys are dynamic we have to use Object.values(obj) - let superblockProgressJSONArray = Object.values(superblockProgressJSON)[0] - .blocks; - superblockProgressJSONArray.forEach(blockProgressJSON => { - let blockKey = Object.keys(blockProgressJSON)[0]; - let allCompletedChallengesArrayWithTimestamps = - blockProgressJSON[blockKey].completedChallenges; - allCompletedChallengesArrayWithTimestamps.forEach(completionDetails => { - completedTimestampsArray.push(completionDetails.completedDate); + const val = Object.values(superblockProgressJSON)[0]; + if (val && Array.isArray(val.blocks)) { + val.blocks.forEach(blockProgressJSON => { + if (!blockProgressJSON) return; + let blockKey = Object.keys(blockProgressJSON)[0]; + let blockData = blockProgressJSON[blockKey]; + if (blockData && Array.isArray(blockData.completedChallenges)) { + blockData.completedChallenges.forEach(completionDetails => { + if (completionDetails && completionDetails.completedDate) { + completedTimestampsArray.push(completionDetails.completedDate); + } + }); + } }); - }); + } }); return completedTimestampsArray; } @@ -32,11 +42,16 @@ export function extractStudentCompletionTimestamps( */ export function extractFilteredCompletionTimestamps( studentSuperblockProgressJSONArray, - selectedSuperblocks + selectedSuperblocks = [] ) { let completedTimestampsArray = []; + if (!Array.isArray(studentSuperblockProgressJSONArray)) { + return completedTimestampsArray; + } + studentSuperblockProgressJSONArray.forEach(superblockProgressJSON => { + if (!superblockProgressJSON) return; let superblockDashedName = Object.keys(superblockProgressJSON)[0]; // Only include selected superblocks @@ -44,17 +59,21 @@ export function extractFilteredCompletionTimestamps( return; } - let superblockProgressJSONArray = Object.values(superblockProgressJSON)[0] - .blocks; - superblockProgressJSONArray.forEach(blockProgressJSON => { - let blockKey = Object.keys(blockProgressJSON)[0]; - let allCompletedChallengesArrayWithTimestamps = - blockProgressJSON[blockKey].completedChallenges; - - allCompletedChallengesArrayWithTimestamps.forEach(completionDetails => { - completedTimestampsArray.push(completionDetails.completedDate); + const val = Object.values(superblockProgressJSON)[0]; + if (val && Array.isArray(val.blocks)) { + val.blocks.forEach(blockProgressJSON => { + if (!blockProgressJSON) return; + let blockKey = Object.keys(blockProgressJSON)[0]; + let blockData = blockProgressJSON[blockKey]; + if (blockData && Array.isArray(blockData.completedChallenges)) { + blockData.completedChallenges.forEach(completionDetails => { + if (completionDetails && completionDetails.completedDate) { + completedTimestampsArray.push(completionDetails.completedDate); + } + }); + } }); - }); + } }); return completedTimestampsArray; diff --git a/util/student/fetchStudentData.js b/util/student/fetchStudentData.js index 3efd72730..8a123d900 100644 --- a/util/student/fetchStudentData.js +++ b/util/student/fetchStudentData.js @@ -35,9 +35,8 @@ export async function fetchClassroomStudentData(students) { // Dynamic import keeps challengeMapUtils (which uses Node's `fs`) out of // the client bundle — it is only ever called server-side inside // getServerSideProps. - const { resolveAllStudentsToDashboardFormat } = await import( - '../challengeMapUtils' - ); + const { resolveAllStudentsToDashboardFormat } = + await import('../challengeMapUtils'); return resolveAllStudentsToDashboardFormat(emailKeyedData); } @@ -47,6 +46,24 @@ export async function fetchClassroomStudentData(students) { * @deprecated Use fetchClassroomStudentData with fCC API in production. */ export async function fetchStudentData() { - let data = await fetch(process.env.MOCK_USER_DATA_URL); - return data.json(); + try { + if (!process.env.MOCK_USER_DATA_URL) { + console.warn('MOCK_USER_DATA_URL environment variable is not defined.'); + return []; + } + let data = await fetch(process.env.MOCK_USER_DATA_URL); + if (!data.ok) { + console.error( + `Failed to fetch student data: ${data.status} ${data.statusText}` + ); + return []; + } + return await data.json(); + } catch (error) { + console.error( + 'Error fetching student data (mock-fcc-data server is likely down):', + error.message || error + ); + return []; + } }