From 9a093f4503a0129617776adc82bc125c156b76f9 Mon Sep 17 00:00:00 2001 From: Natalia Gill Date: Sat, 30 Sep 2023 21:35:19 +0000 Subject: [PATCH 1/2] Updated details dashboard logic to avoid merging 3 APIs and utilize funtions instead. Updated snapshot tests to accomodate new props associated with details dashboard ammendment. Minor name changes to old variables to be more specific Co-authored-by: Guillermo Flores V Co-authored-by: Komal Kaur Co-authored-by: Utsab Saha Co-authored-by: Edgar Peralta --- __tests__/components/dashtable_v2.test.jsx | 16 +- components/DetailsCSS.module.css | 62 ++++ components/DetailsDashboard.js | 46 +++ components/DetailsDashboardList.js | 70 ++++ components/dashtable_v2.js | 11 +- pages/dashboard/v2/[id].js | 51 ++- .../v2/details/[id]/[studentEmail].js | 53 ++- testing_data/testing-data.jsx | 3 - util/api_proccesor.js | 342 ++++++++++++------ 9 files changed, 514 insertions(+), 140 deletions(-) create mode 100644 components/DetailsCSS.module.css create mode 100644 components/DetailsDashboard.js create mode 100644 components/DetailsDashboardList.js diff --git a/__tests__/components/dashtable_v2.test.jsx b/__tests__/components/dashtable_v2.test.jsx index 88870415..3563f05e 100644 --- a/__tests__/components/dashtable_v2.test.jsx +++ b/__tests__/components/dashtable_v2.test.jsx @@ -2,14 +2,21 @@ import { render } from '@testing-library/react'; import React from 'react'; import GlobalDashboardTable from '../../components/dashtable_v2.js'; -import {studentData, certifications, classroomId, timestamps, totalChallenges} from '../../testing_data/testing-data'; +import { + studentData, + classroomId, + studentsAreEnrolledInSuperblocks, + totalChallenges +} from '../../testing_data/testing-data'; describe('GlobalDashboardTable', () => { // Define a fixed time in milliseconds const fixedTime = new Date('2016-03-09T12:00:00Z').getTime(); // Spy on Date.prototype.getTime and mock its implementation - const getTimeSpy = jest.spyOn(Date.prototype, 'getTime').mockImplementation(() => fixedTime); + const getTimeSpy = jest + .spyOn(Date.prototype, 'getTime') + .mockImplementation(() => fixedTime); // Restore the original getTime method after the test afterEach(() => { @@ -20,13 +27,12 @@ describe('GlobalDashboardTable', () => { const { container } = render( ); expect(getTimeSpy).toHaveBeenCalled(); expect(container).toMatchSnapshot(); }); -}); +}); \ No newline at end of file diff --git a/components/DetailsCSS.module.css b/components/DetailsCSS.module.css new file mode 100644 index 00000000..255e0507 --- /dev/null +++ b/components/DetailsCSS.module.css @@ -0,0 +1,62 @@ +.student_header { + display: flex; + font-size: 1.1rem; + margin: 10px; + border-bottom: 2px solid purple; + color: navy; + font-weight: 100; + background-color: hsl(194, 35%, 76%); + max-width: fit-content; +} + +.board_container { + border: 4px solid black; + padding: 10px; + margin: 0px 5px 1px 5px; + background-color: grey; + color: white; +} + +.list_container { + display: flex; + + flex-direction: row; + align-items: center; + gap: 10px; +} + +.list_container h1 { + font-weight: bold; + font-size: 1.2rem; +} +.list_container button { + font-size: 0.7rem; + text-transform: uppercase; + border: 1px solid navy; + + padding: 5px 10px; + background-color: orange; +} + +.list_container button:hover { + background-color: rgb(89, 103, 174); + color: white; +} +.inner_comp { + background-color: grey; + color: white; +} + +.details_progress_stats { + background-color: rgb(27, 15, 86); + color: white; + display: flex; + flex-direction: row; + justify-content: space-between; + border: 1px solid rgb(93, 0, 255); + padding: 10px; +} + +.detailsBlockTitle { + font-size: 1; +} diff --git a/components/DetailsDashboard.js b/components/DetailsDashboard.js new file mode 100644 index 00000000..af9f9e38 --- /dev/null +++ b/components/DetailsDashboard.js @@ -0,0 +1,46 @@ +import React from 'react'; +import styles from './DetailsCSS.module.css'; +import DetailsDashboardList from './DetailsDashboardList'; +//getStudentProgressInSuperblock + +import { getStudentProgressInSuperblock } from '../util/api_proccesor'; + +export default function DetailsDashboard(props) { + const printSuperblockTitle = individualSuperblockJSON => { + let indexOfTitleInSuperblockTitlesArray = + props.superblocksDetailsJSONArray.indexOf(individualSuperblockJSON); + let superblockTitle = + props.superblockTitles[indexOfTitleInSuperblockTitlesArray]; + return superblockTitle; + }; + + const superblockProgress = superblockDashedName => { + let studentProgress = props.studentData; + + return getStudentProgressInSuperblock( + studentProgress, + superblockDashedName + ); + }; + + return ( + <> + {props.superblocksDetailsJSONArray.map((arrayOfBlockObjs, idx) => { + let index = props.superblocksDetailsJSONArray.indexOf(arrayOfBlockObjs); + let superblockDashedName = + props.superblocksDetailsJSONArray[index][0].superblock; + let progressInBlocks = superblockProgress(superblockDashedName); + let superblockTitle = printSuperblockTitle(arrayOfBlockObjs); + return ( +
+ +
+ ); + })} + + ); +} diff --git a/components/DetailsDashboardList.js b/components/DetailsDashboardList.js new file mode 100644 index 00000000..899c381d --- /dev/null +++ b/components/DetailsDashboardList.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { useState } from 'react'; +import styles from './DetailsCSS.module.css'; +import { getStudentTotalChallengesCompletedInBlock } from '../util/api_proccesor'; + +export default function DetailsDashboardList(props) { + const [hideDetails, setHideDetails] = useState(true); + const [buttonText, setButtonText] = useState('View details'); + + const handleShowDetails = () => { + if (hideDetails) { + setHideDetails(false); + } else { + setHideDetails(true); + } + + handleButtonText(hideDetails); + }; + + const handleButtonText = hideDetails => { + if (hideDetails) { + setButtonText('View less'); + } else { + setButtonText('View details'); + } + }; + + const getStudentsProgressInBlock = blockName => { + return getStudentTotalChallengesCompletedInBlock( + props.studentProgressInBlocks, + blockName + ); + }; + + return ( + <> +
+

{props.superblockTitle}

+ + +
+
+ {hideDetails ? ( + '' + ) : ( + <> +
    +
  • + {props.blockData.map((blockDetails, idx) => { + return ( +
    +

    + {blockDetails.blockName} +

    +

    + {getStudentsProgressInBlock(blockDetails.selector) + + '/' + + blockDetails.allChallenges.length} +

    +
    + ); + })} +
  • +
+ + )} +
+ + ); +} diff --git a/components/dashtable_v2.js b/components/dashtable_v2.js index de66277f..19cfe1ef 100644 --- a/components/dashtable_v2.js +++ b/components/dashtable_v2.js @@ -1,25 +1,24 @@ import { useTable } from 'react-table'; import React from 'react'; import getStudentActivity from './studentActivity'; +import { extractStudentCompletionTimestamps } from '../util/api_proccesor'; export default function GlobalDashboardTable(props) { let grandTotalChallenges = props.totalChallenges; + let rawStudentSummary = props.studentData.map(studentJSON => { let email = studentJSON.email; let completionTimestamps = []; - props.timestamps.forEach(timestampObj => { - if (timestampObj.name === email) { - completionTimestamps = timestampObj.completedTimestamps; - } - }); + completionTimestamps = extractStudentCompletionTimestamps( + studentJSON.certifications + ); let rawStudentActivity = { recentCompletions: completionTimestamps }; let studentActivity = getStudentActivity(rawStudentActivity); - let numCompletions = completionTimestamps.length; let percentageCompletion = ( diff --git a/pages/dashboard/v2/[id].js b/pages/dashboard/v2/[id].js index 931bd7c9..e6edeeb9 100644 --- a/pages/dashboard/v2/[id].js +++ b/pages/dashboard/v2/[id].js @@ -7,12 +7,12 @@ import { getSession } from 'next-auth/react'; import GlobalDashboardTable from '../../../components/dashtable_v2'; import React from 'react'; import { - createDashboardObject, - getTotalChallenges, + createSuperblockDashboardObject, + getTotalChallengesForSuperblocks, getDashedNamesURLs, getSuperBlockJsons, - formattedStudentData, - getCompletionTimestamps + fetchStudentData, + checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher } from '../../../util/api_proccesor'; export async function getServerSideProps(context) { @@ -58,35 +58,52 @@ export async function getServerSideProps(context) { } }); - let formattedStudentDataResponse = await formattedStudentData(); - - let timestamps = getCompletionTimestamps(formattedStudentDataResponse); - let superblockURLS = await getDashedNamesURLs( certificationNumbers.fccCertifications ); - let superBlockJsons = await getSuperBlockJsons(superblockURLS); - let dashboardObjs = createDashboardObject(superBlockJsons); - let totalChallenges = getTotalChallenges(dashboardObjs); + let superBlockJsons = await getSuperBlockJsons(superblockURLS); // this is an array of urls + let dashboardObjs = await createSuperblockDashboardObject(superBlockJsons); + + let totalChallenges = getTotalChallengesForSuperblocks(dashboardObjs); + + let studentData = await fetchStudentData(); + + // Temporary check to map/accomodate hard-coded mock student data progress in unselected superblocks by teacher + let studentsAreEnrolledInSuperblocks = + checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher( + studentData, + dashboardObjs + ); + studentData.forEach(studentJSON => { + let indexToCheckProgress = studentData.indexOf(studentJSON); + let isStudentEnrolledInAtLeastOneSuperblock = + studentsAreEnrolledInSuperblocks[indexToCheckProgress].every( + val => val === true + ); + + if (!isStudentEnrolledInAtLeastOneSuperblock) { + studentData[indexToCheckProgress].certifications = []; + } + }); return { props: { userSession, classroomId: context.params.id, - studentData: formattedStudentDataResponse, + studentData, totalChallenges: totalChallenges, - timestamps: timestamps + studentsAreEnrolledInSuperblocks } }; } export default function Home({ userSession, - studentData, classroomId, totalChallenges, - timestamps + studentData, + studentsAreEnrolledInSuperblocks }) { return ( @@ -106,10 +123,10 @@ export default function Home({ )} diff --git a/pages/dashboard/v2/details/[id]/[studentEmail].js b/pages/dashboard/v2/details/[id]/[studentEmail].js index 5b567c16..e0369acf 100644 --- a/pages/dashboard/v2/details/[id]/[studentEmail].js +++ b/pages/dashboard/v2/details/[id]/[studentEmail].js @@ -4,7 +4,16 @@ import Link from 'next/link'; import prisma from '../../../../../prisma/prisma'; import Navbar from '../../../../../components/navbar'; import { getSession } from 'next-auth/react'; +import { + getDashedNamesURLs, + getSuperBlockJsons, + createSuperblockDashboardObject, + getSuperblockTitlesInClassroomByIndex, + getIndividualStudentData +} from '../../../../../util/api_proccesor'; import React from 'react'; +import styles from '../../../../../components/DetailsCSS.module.css'; +import DetailsDashboard from '../../../../../components/DetailsDashboard'; export async function getServerSideProps(context) { //making sure User is the teacher of this classsroom's dashboard @@ -51,10 +60,37 @@ export async function getServerSideProps(context) { return {}; } + const certificationNumbers = await prisma.classroom.findUnique({ + where: { + classroomId: context.params.id + }, + select: { + fccCertifications: true + } + }); + + let superblockTitles = await getSuperblockTitlesInClassroomByIndex( + certificationNumbers.fccCertifications + ); + + let superblockURLS = await getDashedNamesURLs( + certificationNumbers.fccCertifications + ); + + let superBlockJsons = await getSuperBlockJsons(superblockURLS); // this is an array of urls + let superblocksDetailsJSONArray = await createSuperblockDashboardObject( + superBlockJsons + ); + + let studentData = await getIndividualStudentData(studentEmail); + return { props: { userSession, studentEmail, + superblockTitles, + superblocksDetailsJSONArray, + studentData, classroomName: classroomName.classroomName } }; @@ -63,6 +99,9 @@ export async function getServerSideProps(context) { export default function StudentDetails({ userSession, studentEmail, + superblocksDetailsJSONArray, + superblockTitles, + studentData, classroomName }) { return ( @@ -82,9 +121,17 @@ export default function StudentDetails({ Menu -

- {studentEmail}'s progress in {classroomName} -

+
+

+ {studentEmail}'s progress in {classroomName} +

+
+ + )}
diff --git a/testing_data/testing-data.jsx b/testing_data/testing-data.jsx index 03be5052..81cab66d 100644 --- a/testing_data/testing-data.jsx +++ b/testing_data/testing-data.jsx @@ -132,10 +132,7 @@ export const certifications = [ } ] ]; - export const classroomId = 'cljbqrfoa0006kohk894ym6v3'; -export const userId = 'cljyeiqeq0000e1k5fyxg20pa'; - export const timestamps = [ { diff --git a/util/api_proccesor.js b/util/api_proccesor.js index cdc67e8b..5c29aafd 100644 --- a/util/api_proccesor.js +++ b/util/api_proccesor.js @@ -2,12 +2,99 @@ export const FCC_BASE_URL = 'https://www.freecodecamp.org/curriculum-data/v1/'; export const AVAILABLE_SUPER_BLOCKS = FCC_BASE_URL + 'available-superblocks.json'; -/* This function returns a 2D array for each block within blocks -block[0] is the name of the course -block[1] is a dictionary {desc, challenges} -Example Usage: sortSuperBlocks("2022/responsive-web-design.json", "https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json") -*/ +/** ============ getAllTitlesAndDashedNamesSuperblockJSONArray() ============ */ +export async function getAllTitlesAndDashedNamesSuperblockJSONArray() { + // calls this API https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json + const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); + + // the response of this structure is [ superblocks: [ {}, {}, ...etc] ] + const curriculumData = await superblocksres.json(); + + // which is why we return curriculumData.superblocks + return curriculumData.superblocks; +} + +/** ============ getAllSuperblockTitlesAndDashedNames() ============ */ +export async function getAllSuperblockTitlesAndDashedNames() { + let superblockTitleAndDashedNameJSONArray = + await getAllTitlesAndDashedNamesSuperblockJSONArray(); + + let superblockDashedNameToTitleArrayMapping = []; + superblockTitleAndDashedNameJSONArray.forEach( + superblockDashedNameAndTitleObject => { + let superblockDashedNameToTitleArray = { + superblockDashedName: '', + superblockReadableTitle: '' + }; + let superblockDashedName = superblockDashedNameAndTitleObject.dashedName; + let superblockTitle = superblockDashedNameAndTitleObject.title; + superblockDashedNameToTitleArray.superblockDashedName = + superblockDashedName; + superblockDashedNameToTitleArray.superblockReadableTitle = + superblockTitle; + superblockDashedNameToTitleArrayMapping.push( + superblockDashedNameToTitleArray + ); + } + ); + return superblockDashedNameToTitleArrayMapping; +} + +/** ============ getSuperblockTitlesInClassroomByIndex(fccCertificationsArrayOfIndicies) ============ */ +// The reason we use an array of indicies is because that is how the data is stored in the Classroom table after class creation, see ClassInviteTable.js and modal.js component for more context. +export async function getSuperblockTitlesInClassroomByIndex( + fccCertificationsArrayOfIndicies +) { + let allSuperblockTitles = await getAllSuperblockTitlesAndDashedNames(); + + return fccCertificationsArrayOfIndicies.map( + x => allSuperblockTitles[x].superblockReadableTitle + ); +} + +/** ============ checkIfStudentHasProgressDataForSuperblock(studentJSON, superblockDashboardObj) ============ */ +// Since we are using hard-coded mock data at the moment, this check allows to anticipate the +// correct response, however, when the student API data goes live, it will be assumed that it will on +// provide student data on the specified superblocks selected by the teacher +export function checkIfStudentHasProgressDataForSuperblocksSelectedByTeacher( + studentJSON, + superblockDashboardObj +) { + // Returns a boolean matrix which checks to see enrollment in at least 1 superblock (at least 1 because in the GlobalDashboard component we calculate the cumulative progress) + let superblockTitlesSelectedByTeacher = []; + + superblockDashboardObj.forEach(superblockObj => { + superblockTitlesSelectedByTeacher.push(superblockObj[0].superblock); + }); + + let 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); + }); + studentResponseDataHasSuperblockBooleanArray.push( + individualStudentEnrollmentStatus + ); + }); + + return studentResponseDataHasSuperblockBooleanArray; +} + +/** ============ sortSuperBlocks(superblock) ============ */ +/** + * This function returns a 2D array for each block within blocks + * block[0] is the name of the course + * block[1] is a dictionary {desc, challenges} + * Example Usage: + * sortSuperBlocks("2022/responsive-web-design.json", "https://www.freecodecamp.org/curriculum-data/v1/2022/responsive-web-design.json") + * + */ export function sortSuperBlocks(superblock) { let sortedBlock = superblock.sort((a, b) => a['order'] - b['order']); return sortedBlock; @@ -43,12 +130,30 @@ export async function getDashedNamesURLs(fccCertifications) { ); } -export async function getNonDashedNamesURLs(fccCertifications) { +/** ============ getNonDashedNamesURLs([0,1,2) ============ */ +/** + * The parameter relates to the index found at the following API response + * https://www.freecodecamp.org/curriculum-data/v1/available-superblocks.json + * + * Context: The way we know which superblocks are assigned in the classroom + * is by storing the indicies in our DB (Prisma to access/write) + * [see the Classroom table, then the fccCertifications column] + * if you would like more context see the following file(s): + * pages/classes/index.js and take a look at the Modal component + * (components/modal.js), and also take a look at the + * ClassInviteTable component (component/ClassInviteTable). + * You can also search the codebase for the folling string to get more context + * on the relation on the indicies stored in Prisma (unded the + * fccCertifications column): "Select certifications:" + */ +export async function getNonDashedNamesURLs(fccCertificationsIndex) { const superblocksres = await fetch(AVAILABLE_SUPER_BLOCKS); const curriculumData = await superblocksres.json(); - return fccCertifications.map(x => curriculumData['superblocks'][x]['title']); + return fccCertificationsIndex.map( + x => curriculumData['superblocks'][x]['title'] + ); } /** ============ getSuperBlockJsons(superblockURLS) ============ */ @@ -68,12 +173,12 @@ export async function getNonDashedNamesURLs(fccCertifications) { * * Example output: * [ - * { - * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } - * }, - * { - * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } - * } + * { + * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } + * }, + * { + * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } + * } * ] * * */ @@ -88,7 +193,7 @@ export async function getSuperBlockJsons(superblockURLS) { return responses; } -/** ============ createDashboardObject(superblock) ============ */ +/** ============ createSuperblockDashboardObject(superblock) ============ */ /* * [Parameters] an array of objects containing superblock/certificate information as a parameter. * @@ -97,63 +202,78 @@ export async function getSuperBlockJsons(superblockURLS) { * * Example usage: * createDasboardObject([ - * { - * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } - * }, - * { - * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } - * } + * { + * '2022/responsive-web-design': { intro: [Array], blocks: [Object] } + * }, + * { + * 'javascript-algorithms-and-data-structures': { intro: [Array], blocks: [Object] } + * } *]) * * * * Example output: * [ - * [ - * { - * name: 'Learn HTML by Building a Cat Photo App', - * selector: 'learn-html-by-building-a-cat-photo-app', - * dashedName: 'learn-html-by-building-a-cat-photo-app', - * allChallenges: [Array], - * order: 0 - * }, - * { - * name: 'Learn Basic CSS by Building a Cafe Menu', - * selector: 'learn-basic-css-by-building-a-cafe-menu', - * dashedName: 'learn-basic-css-by-building-a-cafe-menu', - * allChallenges: [Array], - * order: 1 - * } - * ] + * [ + * { + * name: 'Learn HTML by Building a Cat Photo App', + * selector: 'learn-html-by-building-a-cat-photo-app', + * dashedName: 'learn-html-by-building-a-cat-photo-app', + * allChallenges: [Array], + * order: 0 + * }, + * { + * name: 'Learn Basic CSS by Building a Cafe Menu', + * selector: 'learn-basic-css-by-building-a-cafe-menu', + * dashedName: 'learn-basic-css-by-building-a-cafe-menu', + * allChallenges: [Array], + * order: 1 + * } + * ] * ] * */ -export function createDashboardObject(superblock) { +export async function createSuperblockDashboardObject(superblock) { + let superblockDashedNamesAndTitlesArray = + await getAllSuperblockTitlesAndDashedNames(); + let sortedBlocks = superblock.map(currBlock => { let certification = Object.keys(currBlock).map(certificationName => { + let superblockDashedNameAndTitle = + superblockDashedNamesAndTitlesArray.find( + superblockDashedNameAndTitleJSON => + superblockDashedNameAndTitleJSON['superblockDashedName'] === + certificationName + ); + let blockInfo = Object.entries( currBlock[certificationName]['blocks'] ).map(([course]) => { /* - The following object is necessary in order to sort our courses/superblocks correctly in order to pass them into our dashtabs.js component +The following object is necessary in order to sort our courses/superblocks correctly in order to pass them into our dashtabs.js component + - Layout: - blockInfo: This is an array of objects that will be passed into our sorting function. +Layout: +blockInfo: This is an array of objects that will be passed into our sorting function. - name: This is the human readable name of the course - selector: this is for our dashtabs component to have a unique selector for each dynamically generated tab - allChallenges: As the name implies, this holds all of our challenges (inside of the current block) in correct order - - The last bit is the order of the current block inside of the certification, not the challenges that exist inside of this block - */ + +name: This is the human readable name of the course +selector: this is for our dashtabs component to have a unique selector for each dynamically generated tab +allChallenges: As the name implies, this holds all of our challenges (inside of the current block) in correct order +The last bit is the order of the current block inside of the certification, not the challenges that exist inside of this block +*/ let currCourseBlock = { - name: currBlock[certificationName]['blocks'][course]['challenges'][ - 'name' - ], - /* - This selector is changed inside of components/dashtabs.js - If you are having issues with the selector, you should probably check there. - */ + superblock: superblockDashedNameAndTitle.superblockDashedName, + superblockReadableTitle: + superblockDashedNameAndTitle.superblockReadableTitle, + blockName: + currBlock[certificationName]['blocks'][course]['challenges'][ + 'name' + ], + /* +This selector is changed inside of components/dashtabs.js +If you are having issues with the selector, you should probably check there. +*/ selector: course, dashedName: course, allChallenges: @@ -176,82 +296,92 @@ export function createDashboardObject(superblock) { return sortedBlocks.flat(1); } +/** ============ fetchStudentData() ============ */ export async function fetchStudentData() { let data = await fetch(process.env.MOCK_USER_DATA_URL); return data.json(); } -export async function formattedStudentData() { +/** ============ getIndividualStudentData(studentEmail) ============ */ +// Uses for the details page +export async function getIndividualStudentData(studentEmail) { let studentData = await fetchStudentData(); + let individualStudentObj = {}; + studentData.forEach(individualStudentDetailsObj => { + if (individualStudentDetailsObj.email === studentEmail) { + individualStudentObj = individualStudentDetailsObj; + } + }); - let formattedStudentData = []; + return individualStudentObj; +} - studentData.forEach(data => { - let studentObj = { email: '', superblocks: [] }; - studentObj.email = data.email; +/** ============ getTotalChallengesForSuperblocks(superblockDasboardObj) ============ */ +export function getTotalChallengesForSuperblocks(superblockDasboardObj) { + let totalChallengesInSuperblock = 0; + superblockDasboardObj.forEach(blockObjArray => { + blockObjArray.forEach(blockObj => { + totalChallengesInSuperblock += blockObj.allChallenges.length; + }); + }); - data.certifications.forEach(superblockObject => { - let superBlockName = Object.keys(superblockObject)[0]; // since we're extracting 1 key at a time it's always going to return an array with 1 item - let superblockFormattedObj = { name: superBlockName, blocks: [] }; + return totalChallengesInSuperblock; +} - superblockObject[superBlockName].blocks.forEach(blockObj => { - let blockName = Object.keys(blockObj)[0]; // since we're extracting 1 key at a time it's always going to return an array with 1 item - let challengeData = blockObj[blockName].completedChallenges; +/** ============ extractStudentCompletionTimestamps(studentSuperblockProgressJSONArray) ============ */ +export function extractStudentCompletionTimestamps( + studentSuperblockProgressJSONArray +) { + let completedTimestampsArray = []; - superblockFormattedObj.blocks.push({ - name: blockName, - challengeData: challengeData - }); + studentSuperblockProgressJSONArray.forEach(superblockProgressJSON => { + // 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); }); - - studentObj.superblocks.push(superblockFormattedObj); }); - - formattedStudentData.push(studentObj); }); - - return formattedStudentData; + return completedTimestampsArray; } -// used for drop down in details page -export async function getIndividualStudentData(studentEmail) { - let studentData = await formattedStudentData(); +/** ============ getStudentProgressInSuperblock(studentSuperblocksJSON, specificSuperblockDashedName) ============ */ +export function getStudentProgressInSuperblock( + studentSuperblocksJSON, + specificSuperblockDashedName +) { + let blockProgressDetails = []; - let individualStudentObj = {}; - studentData.forEach(data => { - if (data.email === studentEmail) { - individualStudentObj = data; + studentSuperblocksJSON.certifications.forEach(superblockProgressJSON => { + // 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; } }); - return individualStudentObj; -} - -export function getTotalChallenges(dashboardObj) { - let total = 0; - dashboardObj.forEach(blockObjArray => { - blockObjArray.forEach(blockObj => { - total += blockObj.allChallenges.length; - }); - }); - - return total; + return blockProgressDetails; } -export function getCompletionTimestamps(studentData) { - let timestamps = []; +/** ============ getStudentTotalChallengesCompletedInBlock(studentProgressInBlock,blockName) ============ */ +export function getStudentTotalChallengesCompletedInBlock( + studentProgressInBlock, + blockName +) { + let totalChallengesCompletedInBlock = 0; + studentProgressInBlock.forEach(blockProgressObj => { + let blockTitle = Object.keys(blockProgressObj)[0]; - studentData.forEach(data => { - let studentObj = { name: data.email, completedTimestamps: [] }; - data.superblocks.forEach(superblock => { - superblock.blocks.forEach(block => { - block.challengeData.forEach(data => { - studentObj.completedTimestamps.push(data.completedDate); - }); - }); - }); - timestamps.push(studentObj); + if (blockTitle === blockName) { + totalChallengesCompletedInBlock = + blockProgressObj[blockTitle].completedChallenges.length; + } }); - return timestamps; + return totalChallengesCompletedInBlock; } From 2aa513b917e98e658e9523835e91af3b731abcb3 Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Sat, 30 Sep 2023 21:36:28 +0000 Subject: [PATCH 2/2] [CodeFactor] Apply fixes to commit 9a093f4 --- components/DetailsCSS.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DetailsCSS.module.css b/components/DetailsCSS.module.css index 255e0507..0a93b1d3 100644 --- a/components/DetailsCSS.module.css +++ b/components/DetailsCSS.module.css @@ -12,7 +12,7 @@ .board_container { border: 4px solid black; padding: 10px; - margin: 0px 5px 1px 5px; + margin: 0px 5px 1px; background-color: grey; color: white; }