From e6fb85e1ce24bc63441fe82a0c64527c16730e5b Mon Sep 17 00:00:00 2001 From: Mandy-cyber Date: Fri, 21 Jul 2023 22:33:29 -0400 Subject: [PATCH 1/8] added endpoint for grouped hacker data --- .../controllers/sortedHackers-controller.ts | 8 +- .../data/json_outputs/sortedHackers.json | 129 +++++++++++- .../routes/sortedHackers-routes.ts | 2 + .../service/sortedHackers-service.ts | 184 +++++++++++++++--- backend/judging-algorithm/yarn.lock | 18 ++ ui/package.json | 1 + ui/pages/cabinSorting.tsx | 32 ++- yarn.lock | 16 +- 8 files changed, 354 insertions(+), 36 deletions(-) diff --git a/backend/cabin-sorting/controllers/sortedHackers-controller.ts b/backend/cabin-sorting/controllers/sortedHackers-controller.ts index cc5b8c2..42b7c0e 100644 --- a/backend/cabin-sorting/controllers/sortedHackers-controller.ts +++ b/backend/cabin-sorting/controllers/sortedHackers-controller.ts @@ -14,4 +14,10 @@ const createSortedHacker = async (req: any, res: any) => { return createResponse; }; -export default {getSortedHackers, createSortedHacker}; \ No newline at end of file +const getGroupedHackers = async (_req: any, res: any) => { + const groupedHackers = await sortedHackersService.getGroupedHackers() + res.json(groupedHackers) + return groupedHackers +} + +export default {getSortedHackers, createSortedHacker, getGroupedHackers }; \ No newline at end of file diff --git a/backend/cabin-sorting/data/json_outputs/sortedHackers.json b/backend/cabin-sorting/data/json_outputs/sortedHackers.json index 81291e6..f3220fb 100644 --- a/backend/cabin-sorting/data/json_outputs/sortedHackers.json +++ b/backend/cabin-sorting/data/json_outputs/sortedHackers.json @@ -1 +1,128 @@ -[{"question0":"answer1","question1":"answer1","question2":"answer1","question3":"answer1","question4":"answer1","question5":"answer1","question6":"answer1","question7":"answer1","question8":"answer1","question9":"answer1","question10":"answer1","question11":"answer1","id":"64accf3dd36486421bc50e53","email":"test","assignedCabin":"cabin1","secondAssignedCabin":" cabin2"},{"question0":"answer2","question1":"answer2","question2":"answer2","question3":"answer2","question4":"answer2","question5":"answer2","question6":"answer2","question7":"answer2","question8":"answer2","question9":"answer2","question10":"answer2","question11":"answer2","id":"64accf66d36486421bc50e55","email":"test2","assignedCabin":" cabin2","secondAssignedCabin":"cabin1"},{"question0":"answer3","question1":"answer3","question2":"answer3","question3":"answer3","question4":"answer3","question5":"answer3","question6":"answer3","question7":"answer3","question8":"answer3","question9":"answer3","question10":"answer3","question11":"answer3","id":"64accf7fd36486421bc50e57","email":"test3","assignedCabin":" cabin3","secondAssignedCabin":"cabin1"},{"question0":"answer4","question1":"answer4","question2":"answer4","question3":"answer4","question4":"answer4","question5":"answer4","question6":"answer4","question7":"answer4","question8":"answer4","question9":"answer4","question10":"answer4","question11":"answer4","id":"64accf8ed36486421bc50e59","email":"test4","assignedCabin":" cabin4","secondAssignedCabin":"cabin1"}] \ No newline at end of file +[ + { + "id": "64accf3dd36486421bc50e53", + "email": "test", + "question0": "answer1", + "question1": "answer1", + "question2": "answer1", + "question3": "answer1", + "question4": "answer1", + "question5": "answer1", + "question6": "answer1", + "question7": "answer1", + "question8": "answer1", + "question9": "answer1", + "question10": "answer1", + "question11": "answer1", + "assignedCabin": "cabin1", + "secondAssignedCabin": "cabin2" + }, + { + "id": "64accf66d36486421bc50e55", + "email": "test2", + "question0": "answer2", + "question1": "answer2", + "question2": "answer2", + "question3": "answer2", + "question4": "answer2", + "question5": "answer2", + "question6": "answer2", + "question7": "answer2", + "question8": "answer2", + "question9": "answer2", + "question10": "answer2", + "question11": "answer2", + "assignedCabin": "cabin2", + "secondAssignedCabin": "cabin1" + }, + { + "id": "64accf7fd36486421bc50e57", + "email": "test3", + "question0": "answer3", + "question1": "answer3", + "question2": "answer3", + "question3": "answer3", + "question4": "answer3", + "question5": "answer3", + "question6": "answer3", + "question7": "answer3", + "question8": "answer3", + "question9": "answer3", + "question10": "answer3", + "question11": "answer3", + "assignedCabin": "cabin3", + "secondAssignedCabin": "cabin1" + }, + { + "id": "64accf8ed36486421bc50e59", + "email": "test4", + "question0": "answer4", + "question1": "answer4", + "question2": "answer4", + "question3": "answer4", + "question4": "answer4", + "question5": "answer4", + "question6": "answer4", + "question7": "answer4", + "question8": "answer4", + "question9": "answer4", + "question10": "answer4", + "question11": "answer4", + "assignedCabin": "cabin4", + "secondAssignedCabin": "cabin1" + }, + { + "id": "64ae0242cdcfed39b8b59958", + "email": "test4", + "question0": "answer4", + "question1": "answer4", + "question2": "answer4", + "question3": "answer4", + "question4": "answer4", + "question5": "answer4", + "question6": "answer4", + "question7": "answer4", + "question8": "answer4", + "question9": "answer4", + "question10": "answer4", + "question11": "answer4", + "assignedCabin": "cabin4", + "secondAssignedCabin": "cabin1" + }, + { + "id": "64ae024dcdcfed39b8b5995a", + "email": "test4", + "question0": "answer4", + "question1": "answer4", + "question2": "answer4", + "question3": "answer4", + "question4": "answer4", + "question5": "answer4", + "question6": "answer4", + "question7": "answer4", + "question8": "answer4", + "question9": "answer4", + "question10": "answer4", + "question11": "answer4", + "assignedCabin": "cabin4", + "secondAssignedCabin": "cabin1" + }, + { + "id": "64b0a5b67c6e7f3511e8509e", + "email": "test4", + "question0": "answer4", + "question1": "answer4", + "question2": "answer4", + "question3": "answer4", + "question4": "answer4", + "question5": "answer4", + "question6": "answer4", + "question7": "answer4", + "question8": "answer4", + "question9": "answer4", + "question10": "answer4", + "question11": "answer4", + "assignedCabin": "cabin4", + "secondAssignedCabin": "cabin1" + } +] \ No newline at end of file diff --git a/backend/cabin-sorting/routes/sortedHackers-routes.ts b/backend/cabin-sorting/routes/sortedHackers-routes.ts index c37c86a..f0c1dc4 100644 --- a/backend/cabin-sorting/routes/sortedHackers-routes.ts +++ b/backend/cabin-sorting/routes/sortedHackers-routes.ts @@ -6,4 +6,6 @@ const router = express.Router(); router.get('/sortedHackers', controller.getSortedHackers); router.post('/sortedHackers', controller.createSortedHacker); +router.get('/groupedHackers', controller.getGroupedHackers); + export default router; diff --git a/backend/cabin-sorting/service/sortedHackers-service.ts b/backend/cabin-sorting/service/sortedHackers-service.ts index 1733daf..0ad6284 100644 --- a/backend/cabin-sorting/service/sortedHackers-service.ts +++ b/backend/cabin-sorting/service/sortedHackers-service.ts @@ -1,35 +1,171 @@ -import { Types } from "mongoose"; -import * as sortedHackersDao from "../dao/sortedHackers-dao.js"; +import * as fs from 'fs'; +import * as path from 'path'; +import { parse } from 'csv-parse/sync'; +import { Types } from 'mongoose'; +import * as sortedHackersDao from '../dao/sortedHackers-dao.js'; + +let hackerList: any[]; +let answerList: any[]; +let cabinList: any[]; +let CABIN_SIZE: number; +let QUESTIONS_SIZE: number; interface HackerDataType { - id: string, - email: string, - [key: string] : string, + id: string; + email: string; + [key: string]: string; } const getSortedHackers = async () => { - const rawHackerData = await sortedHackersDao.getSortedHackers(); - const formattedHackerData = rawHackerData.map(hackerData => { - const {_id, email, applicationResponses} = hackerData - const initialHackerData : HackerDataType = { - id: _id.toString(), - email: email, - } + const rawHackerData = await sortedHackersDao.getSortedHackers(); + const formattedHackerData = rawHackerData.map((hackerData) => { + const { _id, email, applicationResponses } = hackerData; + const initialHackerData: HackerDataType = { + id: _id.toString(), + email: email + }; - function formatApplicationResponses(accumulatedResponse : HackerDataType, currentResponseKey : any, currentResponseIndex : number) { - const currentResponseQuestionKey = "question" + currentResponseIndex; - accumulatedResponse[currentResponseQuestionKey] = applicationResponses[currentResponseKey] - return accumulatedResponse - } + function formatApplicationResponses( + accumulatedResponse: HackerDataType, + currentResponseKey: any, + currentResponseIndex: number + ) { + const currentResponseQuestionKey = 'question' + currentResponseIndex; + accumulatedResponse[currentResponseQuestionKey] = + applicationResponses[currentResponseKey]; + return accumulatedResponse; + } - return Object.keys(applicationResponses).reduce(formatApplicationResponses, initialHackerData) - }) - return formattedHackerData; + return Object.keys(applicationResponses).reduce( + formatApplicationResponses, + initialHackerData + ); + }); + return formattedHackerData; }; -const createSortedHacker = async (hacker : any) => { - const createResponse = await sortedHackersDao.createdSortedHacker(hacker) +const createSortedHacker = async (hacker: any) => { + const createResponse = await sortedHackersDao.createdSortedHacker(hacker); return createResponse; }; - -export default {getSortedHackers, createSortedHacker}; \ No newline at end of file + +const getGroupedHackers = async () => { + const hackers = await assignHackerCabins(); + console.log(hackers) + let cabinEmails: string[][] = []; + + hackers.forEach((hacker) => { + console.log(hacker) + }) + + const groupedHackers = hackers.reduce((accum, currVal) => { + const assignedCabin = currVal.assignedCabin; + const cabinNum = cabinList.indexOf(assignedCabin); + if (cabinNum === -1) { + console.error( + `Cabin assigned to Hacker ${assignedCabin.id} could not be found` + ); + return accum; + } + const hackerEmail = currVal.email; + if (!accum[cabinNum]) { + accum[cabinNum] = [] + } + accum[cabinNum].push(hackerEmail); + + console.log(accum) + return accum; + }, cabinEmails); + + return groupedHackers +}; + +function loadCSV(filepath: string, headers: boolean): any[] { + const csvFileAbsolutePath = path.resolve( + 'cabin-sorting', + 'data', + 'csv_inputs', + filepath); + console.log(csvFileAbsolutePath) + + // error handling in case file is missing + let fileContent; + try { + fileContent = fs.readFileSync(csvFileAbsolutePath, { + encoding: 'utf-8' + }); + } catch (err) { + console.log(`File cannot be found: "${filepath}"`); + return []; + } + + const options = { + delimiter: ',', + columns: headers + }; + return parse(fileContent, options); +} + +async function assignHackerCabins() { + hackerList = await getSortedHackers(); + answerList = loadCSV('answer.csv', true); + cabinList = loadCSV('cabinTypes.csv', false)[0]; + + CABIN_SIZE = Object.keys(cabinList).length; + QUESTIONS_SIZE = Object.keys(answerList[0]).length; + + // ensuring the CSV files exists before continuing + if ( + hackerList.length === 0 || + answerList.length === 0 || + cabinList.length === 0 + ) { + console.error( + 'Please add the respective csv file(s) to the folder to run the sorting algorithm' + ); + return []; + } else { + matchAnswers(); + return hackerList; + } +} + +function matchAnswers() { + hackerList.forEach((hacker: any) => { + // each element = a different cabin, all initialized to 0 + const cabinScore = Array(CABIN_SIZE).fill(0); + + hydrateCabinScore(hacker, cabinScore); + + // create extra column for hacker that determines the cabin they should + // join (the one with the most points) + const cabinOptions: string[] = Object.values(cabinList); + const maxIndex: number = cabinScore.indexOf(Math.max(...cabinScore)); + hacker.assignedCabin = cabinOptions[maxIndex]; + + // find backup cabin for hacker (in case the first choice fills up) + const counterCopy = cabinScore.slice(); + counterCopy[maxIndex] = -1; + hacker.secondAssignedCabin = + cabinOptions[counterCopy.indexOf(Math.max(...counterCopy))]; + }); +} + +function hydrateCabinScore(hacker: any, cabinScore: number[]) { + answerList.forEach((cabin: any, cabinIndex: number) => { + for ( + let questionIndex = 0; + questionIndex < QUESTIONS_SIZE; + questionIndex++ + ) { + if ( + cabin['question' + questionIndex.toString()] === + hacker['question' + questionIndex.toString()] + ) { + cabinScore[cabinIndex]++; + } + } + }); +} + +export default { getSortedHackers, createSortedHacker, getGroupedHackers }; diff --git a/backend/judging-algorithm/yarn.lock b/backend/judging-algorithm/yarn.lock index 33402d1..e44865b 100644 --- a/backend/judging-algorithm/yarn.lock +++ b/backend/judging-algorithm/yarn.lock @@ -2353,6 +2353,11 @@ babel-preset-jest@^29.5.0: babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -2372,6 +2377,14 @@ bowser@^2.11.0: resolved "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz" integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + braces@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" @@ -2535,6 +2548,11 @@ commondir@^1.0.1: resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" diff --git a/ui/package.json b/ui/package.json index dfaaa6e..5f34da1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,6 +16,7 @@ "@mui/x-date-pickers": "^5.0.0-beta.1", "@types/react-material-ui-form-validator": "^2.1.1", "antd": "^4.21.3", + "axios": "^1.4.0", "csv": "^6.3.0", "form-data": "^4.0.0", "mailgun.js": "^7.0.4", diff --git a/ui/pages/cabinSorting.tsx b/ui/pages/cabinSorting.tsx index 8a9f64e..a32a17e 100644 --- a/ui/pages/cabinSorting.tsx +++ b/ui/pages/cabinSorting.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import Layout from '../components/layout/Layout' import { Button, Divider, ThemeProvider, Typography } from '@mui/material' import { theme } from '../styles/theme' @@ -7,8 +7,19 @@ import { CSVLink } from 'react-csv' import CSVCabinTable from '../components/csvTable/CSVCabinTable' import SelectedCabin from '../components/templateDropdown/selectedCabin' import BackArrow from '../components/backArrow/backArrow' +import axios from 'axios' export default function CabinSorting () { + + type HackerEmails = { + emails: string[] + } + + type GetGroupedHackersResponse = { + data: HackerEmails[] + } + + const cabinHeaders: string[] = [ 'Cabin 1', 'Cabin 2', @@ -18,14 +29,17 @@ export default function CabinSorting () { 'Cabin 6' ] - const cabinValues: string[][] = [ - ['email1-1', 'email1-2'], - ['email2-1', 'email2-2', 'email2-3'], - ['email3-1', 'email3-2'], - ['email4-1', 'email4-2'], - [], - ['email6-1', 'email6-2'] - ] + const [cabinValues, setCabinValues] = useState([]) + + async function updateCabinValues () { + axios.get(process.env.GROUPED_HACKERS_URL!).then(groupedHackersResponse => { + setCabinValues(groupedHackersResponse.data) + }) + } + + useEffect(() => { + updateCabinValues() + }, []) const rows: string[][] = [[]] Object.values(cabinValues).forEach((value: any, index: number) => { diff --git a/yarn.lock b/yarn.lock index 57ab204..53d2efa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -207,7 +207,7 @@ human-signals@^4.3.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -husky@^8.0.1: +husky@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== @@ -300,6 +300,13 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz" @@ -415,6 +422,13 @@ punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" From f5d6fc94bbac398866cf7ee8839a76de9e2efa77 Mon Sep 17 00:00:00 2001 From: NelsonDong41 Date: Sun, 23 Jul 2023 02:15:13 -0400 Subject: [PATCH 2/8] using Next getStaticProps to pass in environment variables --- backend/app.ts | 5 +- .../templateDropdown/selectedCabin.tsx | 6 ++- ui/pages/cabinSorting.tsx | 49 ++++++++----------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/backend/app.ts b/backend/app.ts index 72551d5..29bdc05 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -8,12 +8,14 @@ import teamsRouter from "./judging-algorithm/routes/teams-routes.js" import cabinsRouter from "./cabin-sorting/routes/sortedHackers-routes.js" const app = express(); +const PORT = process.env.PORT || 4000; app.use(express.json()); app.use( cors({ - origin: 'http://localhost:4000', + // problem lies here + origin: [`http://localhost:${PORT}`, 'http://localhost:3000'], credentials: true }) ); @@ -24,7 +26,6 @@ app.use('/', roomsRouter); app.use('/', rotationTimesRouter); app.use('/', teamsRouter); -const PORT = process.env.PORT || 4000; app.listen(PORT, () => { console.log(`Server is running in http://localhost:${PORT}`); diff --git a/ui/components/templateDropdown/selectedCabin.tsx b/ui/components/templateDropdown/selectedCabin.tsx index a66200c..9754175 100644 --- a/ui/components/templateDropdown/selectedCabin.tsx +++ b/ui/components/templateDropdown/selectedCabin.tsx @@ -26,7 +26,7 @@ export default function SelectedCabin ({ cabinNames, cabinValues }: SelectedCabi const handleChange = (event: SelectChangeEvent) => { const str = event.target.value - setSelectedItem(Number(str.charAt(str.length - 1))) + setSelectedItem(Number(str.charAt(str.length - 1)) - 1) setCopied(false) setOpenSnackBar(false) } @@ -57,6 +57,7 @@ export default function SelectedCabin ({ cabinNames, cabinValues }: SelectedCabi ))} + {copied ? ( { + if (!cabinValues[selectedItem]) { + return + } navigator.clipboard.writeText(cabinValues[selectedItem].toString()) setCopied(true) setOpenSnackBar(true) diff --git a/ui/pages/cabinSorting.tsx b/ui/pages/cabinSorting.tsx index a32a17e..ddb1ec8 100644 --- a/ui/pages/cabinSorting.tsx +++ b/ui/pages/cabinSorting.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React from 'react' import Layout from '../components/layout/Layout' import { Button, Divider, ThemeProvider, Typography } from '@mui/material' import { theme } from '../styles/theme' @@ -8,18 +8,20 @@ import CSVCabinTable from '../components/csvTable/CSVCabinTable' import SelectedCabin from '../components/templateDropdown/selectedCabin' import BackArrow from '../components/backArrow/backArrow' import axios from 'axios' +import 'dotenv/config' +import { GetStaticProps, InferGetStaticPropsType } from 'next' -export default function CabinSorting () { - - type HackerEmails = { - emails: string[] - } - - type GetGroupedHackersResponse = { - data: HackerEmails[] - } - +export const getStaticProps: GetStaticProps<{ + data: string[][] +}> = async () => { + const url = process.env.GROUPED_HACKERS_URL || '' + const res = await axios.get(url) + const data = res.data + return { props: { data } } +} +export default function CabinSorting (props : InferGetStaticPropsType) { + const cabinValues = props.data const cabinHeaders: string[] = [ 'Cabin 1', 'Cabin 2', @@ -29,23 +31,14 @@ export default function CabinSorting () { 'Cabin 6' ] - const [cabinValues, setCabinValues] = useState([]) - - async function updateCabinValues () { - axios.get(process.env.GROUPED_HACKERS_URL!).then(groupedHackersResponse => { - setCabinValues(groupedHackersResponse.data) - }) - } - - useEffect(() => { - updateCabinValues() - }, []) + const rows: string[][] = [] + cabinHeaders.forEach(() => { + rows.push([]) + }) - const rows: string[][] = [[]] - Object.values(cabinValues).forEach((value: any, index: number) => { + cabinValues.forEach((value: any, index: number) => { value.forEach((entry: string, entryIndex: number) => { - if (rows.length < entryIndex + 1) rows.push([]) - rows[entryIndex][index] = entry + rows[index][entryIndex] = entry }) }) @@ -87,11 +80,11 @@ export default function CabinSorting () {
- +
Copy email list
- + From 233cee13a27910f6c278c09745e06ee564d83fb3 Mon Sep 17 00:00:00 2001 From: NelsonDong41 Date: Sun, 23 Jul 2023 19:21:14 -0400 Subject: [PATCH 3/8] Kept copy to clipboard button available at all times --- .../templateDropdown/selectedCabin.tsx | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/ui/components/templateDropdown/selectedCabin.tsx b/ui/components/templateDropdown/selectedCabin.tsx index 9754175..7a34a5f 100644 --- a/ui/components/templateDropdown/selectedCabin.tsx +++ b/ui/components/templateDropdown/selectedCabin.tsx @@ -18,7 +18,10 @@ type SelectedCabinProps = { cabinValues: string[][]; }; -export default function SelectedCabin ({ cabinNames, cabinValues }: SelectedCabinProps) { +export default function SelectedCabin ({ + cabinNames, + cabinValues +}: SelectedCabinProps) { const [selectedItem, setSelectedItem] = React.useState(-1) const [copied, setCopied] = React.useState(false) @@ -58,46 +61,41 @@ export default function SelectedCabin ({ cabinNames, cabinValues }: SelectedCabi ))} - {copied - ? ( + { + if (!cabinValues[selectedItem]) { + return + } + navigator.clipboard.writeText(cabinValues[selectedItem].toString()) + setCopied(true) + setOpenSnackBar(true) + }} + onMouseEnter={() => setHoverCopy(true)} + onMouseLeave={() => setHoverCopy(false)} + > + + + + + + {copied && ( - ) - : ( - selectedItem && ( - { - if (!cabinValues[selectedItem]) { - return - } - navigator.clipboard.writeText(cabinValues[selectedItem].toString()) - setCopied(true) - setOpenSnackBar(true) - }} - onMouseEnter={() => setHoverCopy(true)} - onMouseLeave={() => setHoverCopy(false)} - > - - - - - ) - )} - + )} {openSnackBar && ( Date: Sun, 23 Jul 2023 19:33:36 -0400 Subject: [PATCH 4/8] removed dotenv for vercel deployment --- ui/pages/cabinSorting.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/pages/cabinSorting.tsx b/ui/pages/cabinSorting.tsx index ddb1ec8..6b64265 100644 --- a/ui/pages/cabinSorting.tsx +++ b/ui/pages/cabinSorting.tsx @@ -8,7 +8,6 @@ import CSVCabinTable from '../components/csvTable/CSVCabinTable' import SelectedCabin from '../components/templateDropdown/selectedCabin' import BackArrow from '../components/backArrow/backArrow' import axios from 'axios' -import 'dotenv/config' import { GetStaticProps, InferGetStaticPropsType } from 'next' export const getStaticProps: GetStaticProps<{ From e616d81fd7db5f1ecc5acb4a8a49ed317fdac5d5 Mon Sep 17 00:00:00 2001 From: Mandy-cyber Date: Tue, 1 Aug 2023 22:28:29 -0400 Subject: [PATCH 5/8] resolving comments from review! --- .../controllers/sortedHackers-controller.ts | 54 +++++++++++++------ .../routes/sortedHackers-routes.ts | 2 +- .../schemas/sortedHackers-schema.ts | 1 + .../service/sortedHackers-service.ts | 33 +++++------- 4 files changed, 55 insertions(+), 35 deletions(-) diff --git a/backend/cabin-sorting/controllers/sortedHackers-controller.ts b/backend/cabin-sorting/controllers/sortedHackers-controller.ts index 42b7c0e..50f826b 100644 --- a/backend/cabin-sorting/controllers/sortedHackers-controller.ts +++ b/backend/cabin-sorting/controllers/sortedHackers-controller.ts @@ -1,23 +1,47 @@ import sortedHackersService from "../service/sortedHackers-service.js"; +import express, { Response, Request } from 'express'; - -const getSortedHackers = async (_req: any, res: any) => { - const sortedHackers = await sortedHackersService.getSortedHackers(); - res.json(sortedHackers) - return sortedHackers; +const getSortedHackers = async (_req: Request, res: Response) => { + try { + const sortedHackers = await sortedHackersService.getSortedHackers(); + if (sortedHackers === null || Object.values(sortedHackers).length === 0) { + throw new Error("Sorted hackers were null/empty...") + } + res.json(sortedHackers) + return sortedHackers; + } catch (err) { + res.status(404).json({message: "Failed to get sorted hackers.", err}) + } + return; }; -const createSortedHacker = async (req: any, res: any) => { - const hackerInformation = req.body; - const createResponse = await sortedHackersService.createSortedHacker(hackerInformation) - res.json(createResponse) - return createResponse; +const createSortedHacker = async (req: Request, res: Response) => { + try { + const hackerInformation = req.body; + const createResponse = await sortedHackersService.createSortedHacker(hackerInformation) + if (createResponse === null || Object.values(createResponse).length === 0) { + throw new Error("Sorted hackers were null/empty...") + } + res.json(createResponse) + return createResponse; + } catch (err) { + res.status(404).json({message: "Failed to create sorted hackers.", err}) + } + return; }; -const getGroupedHackers = async (_req: any, res: any) => { - const groupedHackers = await sortedHackersService.getGroupedHackers() - res.json(groupedHackers) - return groupedHackers +const groupHackersByCabin = async (_req: Request, res: Response) => { + try { + const groupedHackers = await sortedHackersService.groupHackersByCabin() + if (groupedHackers === null || Object.values(groupedHackers).length === 0) { + throw new Error("Grouped hackers were null/empty...") + } + res.json(groupedHackers) + return groupedHackers + } catch (err) { + res.status(404).json({message: "Failed to group hackers by cabin.", err}) + } + return; } -export default {getSortedHackers, createSortedHacker, getGroupedHackers }; \ No newline at end of file +export default {getSortedHackers, createSortedHacker, groupHackersByCabin }; \ No newline at end of file diff --git a/backend/cabin-sorting/routes/sortedHackers-routes.ts b/backend/cabin-sorting/routes/sortedHackers-routes.ts index f0c1dc4..bcafb30 100644 --- a/backend/cabin-sorting/routes/sortedHackers-routes.ts +++ b/backend/cabin-sorting/routes/sortedHackers-routes.ts @@ -6,6 +6,6 @@ const router = express.Router(); router.get('/sortedHackers', controller.getSortedHackers); router.post('/sortedHackers', controller.createSortedHacker); -router.get('/groupedHackers', controller.getGroupedHackers); +router.get('/groupedHackers', controller.groupHackersByCabin); export default router; diff --git a/backend/cabin-sorting/schemas/sortedHackers-schema.ts b/backend/cabin-sorting/schemas/sortedHackers-schema.ts index 5813a91..65276c2 100644 --- a/backend/cabin-sorting/schemas/sortedHackers-schema.ts +++ b/backend/cabin-sorting/schemas/sortedHackers-schema.ts @@ -15,6 +15,7 @@ export const sortedHackersSchema = new mongoose.Schema( versionKey: false }, ); + export default sortedHackersSchema; diff --git a/backend/cabin-sorting/service/sortedHackers-service.ts b/backend/cabin-sorting/service/sortedHackers-service.ts index 0ad6284..3ef0016 100644 --- a/backend/cabin-sorting/service/sortedHackers-service.ts +++ b/backend/cabin-sorting/service/sortedHackers-service.ts @@ -3,12 +3,14 @@ import * as path from 'path'; import { parse } from 'csv-parse/sync'; import { Types } from 'mongoose'; import * as sortedHackersDao from '../dao/sortedHackers-dao.js'; +import { FormattedHacker, Hacker } from '../types.js'; +import sortedHackersSchema from '../schemas/sortedHackers-schema.js'; -let hackerList: any[]; -let answerList: any[]; -let cabinList: any[]; let CABIN_SIZE: number; let QUESTIONS_SIZE: number; +let hackerList: HackerDataType[]; +let answerList: string[]; +let cabinList: string[]; interface HackerDataType { id: string; @@ -16,7 +18,7 @@ interface HackerDataType { [key: string]: string; } -const getSortedHackers = async () => { +const getSortedHackers = async (): Promise => { const rawHackerData = await sortedHackersDao.getSortedHackers(); const formattedHackerData = rawHackerData.map((hackerData) => { const { _id, email, applicationResponses } = hackerData; @@ -44,22 +46,18 @@ const getSortedHackers = async () => { return formattedHackerData; }; -const createSortedHacker = async (hacker: any) => { +const createSortedHacker = async (hacker: Hacker): Promise => { const createResponse = await sortedHackersDao.createdSortedHacker(hacker); return createResponse; }; -const getGroupedHackers = async () => { +const groupHackersByCabin = async (): Promise => { const hackers = await assignHackerCabins(); - console.log(hackers) let cabinEmails: string[][] = []; - hackers.forEach((hacker) => { - console.log(hacker) - }) - const groupedHackers = hackers.reduce((accum, currVal) => { - const assignedCabin = currVal.assignedCabin; + const groupedHackers = hackers.reduce((accum, hacker) => { + const {assignedCabin, email} = hacker const cabinNum = cabinList.indexOf(assignedCabin); if (cabinNum === -1) { console.error( @@ -67,13 +65,11 @@ const getGroupedHackers = async () => { ); return accum; } - const hackerEmail = currVal.email; if (!accum[cabinNum]) { accum[cabinNum] = [] } - accum[cabinNum].push(hackerEmail); + accum[cabinNum].push(email); - console.log(accum) return accum; }, cabinEmails); @@ -86,7 +82,6 @@ function loadCSV(filepath: string, headers: boolean): any[] { 'data', 'csv_inputs', filepath); - console.log(csvFileAbsolutePath) // error handling in case file is missing let fileContent; @@ -135,7 +130,7 @@ function matchAnswers() { // each element = a different cabin, all initialized to 0 const cabinScore = Array(CABIN_SIZE).fill(0); - hydrateCabinScore(hacker, cabinScore); + incrementCabinScores(hacker, cabinScore); // create extra column for hacker that determines the cabin they should // join (the one with the most points) @@ -151,7 +146,7 @@ function matchAnswers() { }); } -function hydrateCabinScore(hacker: any, cabinScore: number[]) { +function incrementCabinScores(hacker: any, cabinScore: number[]) { answerList.forEach((cabin: any, cabinIndex: number) => { for ( let questionIndex = 0; @@ -168,4 +163,4 @@ function hydrateCabinScore(hacker: any, cabinScore: number[]) { }); } -export default { getSortedHackers, createSortedHacker, getGroupedHackers }; +export default { getSortedHackers, createSortedHacker, groupHackersByCabin }; From 367a251202335038fd0cf104e8d8364c50a1b921 Mon Sep 17 00:00:00 2001 From: Mandy-cyber Date: Tue, 1 Aug 2023 22:43:51 -0400 Subject: [PATCH 6/8] added id to destructuring --- backend/cabin-sorting/service/sortedHackers-service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/cabin-sorting/service/sortedHackers-service.ts b/backend/cabin-sorting/service/sortedHackers-service.ts index 3ef0016..ab1d47b 100644 --- a/backend/cabin-sorting/service/sortedHackers-service.ts +++ b/backend/cabin-sorting/service/sortedHackers-service.ts @@ -46,7 +46,7 @@ const getSortedHackers = async (): Promise => { return formattedHackerData; }; -const createSortedHacker = async (hacker: Hacker): Promise => { +const createSortedHacker = async (hacker: Hacker) => { const createResponse = await sortedHackersDao.createdSortedHacker(hacker); return createResponse; }; @@ -57,11 +57,11 @@ const groupHackersByCabin = async (): Promise => { const groupedHackers = hackers.reduce((accum, hacker) => { - const {assignedCabin, email} = hacker + const {assignedCabin, email, id} = hacker const cabinNum = cabinList.indexOf(assignedCabin); if (cabinNum === -1) { console.error( - `Cabin assigned to Hacker ${assignedCabin.id} could not be found` + `Cabin assigned to Hacker ${id} could not be found` ); return accum; } From f1a48f91947dcd260225b6f4adebf3671e8070d1 Mon Sep 17 00:00:00 2001 From: Mandy-cyber Date: Tue, 1 Aug 2023 23:07:47 -0400 Subject: [PATCH 7/8] adding return type to createSortedHacker --- backend/cabin-sorting/service/sortedHackers-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/cabin-sorting/service/sortedHackers-service.ts b/backend/cabin-sorting/service/sortedHackers-service.ts index ab1d47b..2f07179 100644 --- a/backend/cabin-sorting/service/sortedHackers-service.ts +++ b/backend/cabin-sorting/service/sortedHackers-service.ts @@ -46,7 +46,7 @@ const getSortedHackers = async (): Promise => { return formattedHackerData; }; -const createSortedHacker = async (hacker: Hacker) => { +const createSortedHacker = async (hacker: Hacker): Promise => { const createResponse = await sortedHackersDao.createdSortedHacker(hacker); return createResponse; }; From 3b24ee6d2efd7576b3a8998e3a7706fabcf7ab1c Mon Sep 17 00:00:00 2001 From: Mandy-cyber Date: Fri, 18 Aug 2023 12:13:51 -0400 Subject: [PATCH 8/8] small changes from review --- .../controllers/sortedHackers-controller.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/cabin-sorting/controllers/sortedHackers-controller.ts b/backend/cabin-sorting/controllers/sortedHackers-controller.ts index 50f826b..87ab2ae 100644 --- a/backend/cabin-sorting/controllers/sortedHackers-controller.ts +++ b/backend/cabin-sorting/controllers/sortedHackers-controller.ts @@ -4,13 +4,13 @@ import express, { Response, Request } from 'express'; const getSortedHackers = async (_req: Request, res: Response) => { try { const sortedHackers = await sortedHackersService.getSortedHackers(); - if (sortedHackers === null || Object.values(sortedHackers).length === 0) { - throw new Error("Sorted hackers were null/empty...") + if (!sortedHackers || Object.values(sortedHackers).length === 0) { + res.status(404).json({message: "Sorted hackers were null/empty..."}) } res.json(sortedHackers) return sortedHackers; } catch (err) { - res.status(404).json({message: "Failed to get sorted hackers.", err}) + res.status(400).json({message: "Failed to get sorted hackers.", err}) } return; }; @@ -19,13 +19,13 @@ const createSortedHacker = async (req: Request, res: Response) => { try { const hackerInformation = req.body; const createResponse = await sortedHackersService.createSortedHacker(hackerInformation) - if (createResponse === null || Object.values(createResponse).length === 0) { - throw new Error("Sorted hackers were null/empty...") + if (!createResponse || Object.values(createResponse).length === 0) { + res.status(404).json({message: "Sorted hackers were null/empty..."}) } res.json(createResponse) return createResponse; } catch (err) { - res.status(404).json({message: "Failed to create sorted hackers.", err}) + res.status(400).json({message: "Failed to create sorted hackers.", err}) } return; }; @@ -33,13 +33,13 @@ const createSortedHacker = async (req: Request, res: Response) => { const groupHackersByCabin = async (_req: Request, res: Response) => { try { const groupedHackers = await sortedHackersService.groupHackersByCabin() - if (groupedHackers === null || Object.values(groupedHackers).length === 0) { - throw new Error("Grouped hackers were null/empty...") + if (!groupedHackers || Object.values(groupedHackers).length === 0) { + res.status(404).json({message: "Grouped hackers were null/empty..."}) } res.json(groupedHackers) return groupedHackers } catch (err) { - res.status(404).json({message: "Failed to group hackers by cabin.", err}) + res.status(400).json({message: "Failed to group hackers by cabin.", err}) } return; }