From 373e4e7923724b238cd3d78ea00400bd3a0ae39a Mon Sep 17 00:00:00 2001 From: Alex B Date: Sat, 23 Dec 2023 19:35:23 +0100 Subject: [PATCH 01/26] Important admin settings (Semester, Slack Webhooks, Questions URL, Rejoin Time) are now saved to a file and loaded if the queue is restarted --- server/controllers/settings.js | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/server/controllers/settings.js b/server/controllers/settings.js index 1551df9..c3ba35c 100644 --- a/server/controllers/settings.js +++ b/server/controllers/settings.js @@ -11,14 +11,36 @@ const home = require('./home'); // Global admin settings // FIXME: some default values are set to simplify testing; // In production, these should be cleared +var fs = require('fs'); let adminSettings = { - currSem: "S23", + currSem: "F23", slackURL: null, questionsURL: '', - rejoinTime: 10 + rejoinTime: 15 }; +// If no admin setting have been generated, use the above default values +if (!fs.existsSync('../adminSettings.json')) { + var json = JSON.stringify(adminSettings) + fs.writeFile('../adminSettings.json', json, 'utf8', function () { + console.log('Created admin settings JSON'); + }); +} exports.get_admin_settings = function () { + + let data = fs.readFileSync('../adminSettings.json', 'utf8', flag = 'r+'); + if (data) { + adminSettings = JSON.parse(data); + } else { + console.log('No admin settings found'); + adminSettings = { + currSem: "F23", + slackURL: null, + questionsURL: '', + rejoinTime: 15 + }; + } + return adminSettings; } @@ -50,6 +72,13 @@ function respond_success(req, res, message = null) { respond(req, res, message, {}, 200); } +function writeAdminSettings (settings) { + var json = JSON.stringify(settings) + fs.writeFileSync('../adminSettings.json', json, 'utf8', function () { + return; + }); +} + /** General Settings **/ exports.post_update_video_chat = function (req, res) { if (!req.user || !req.user.isTA) { @@ -178,6 +207,7 @@ exports.post_update_semester = function (req, res) { } }).then(function (results) { adminSettings.currSem = results[0].sem_id; + writeAdminSettings(adminSettings); respond_success(req, res, `Current semester set to ${sem_id} successfully`); }).catch(err => { message = err.message || "An error occurred while updating current semester"; @@ -200,6 +230,7 @@ exports.post_update_slack_url = function (req, res) { if (adminSettings.slackURL == slackURL) return; adminSettings.slackURL = slackURL; + writeAdminSettings(adminSettings); slack.update_slack(); respond_success(req, res, `Slack Webhook URL updated successfully`); } @@ -219,6 +250,7 @@ exports.post_update_questions_url = function (req, res) { if (adminSettings.questionsURL == questionsURL) return; adminSettings.questionsURL = questionsURL; + writeAdminSettings(adminSettings); home.emit_new_queue_data(); respond_success(req, res, `Questions Guide URL updated successfully`); } @@ -238,6 +270,7 @@ exports.post_update_rejoin_time = function (req, res) { if (adminSettings.rejoinTime == rejoinTime) return; adminSettings.rejoinTime = rejoinTime; + writeAdminSettings(adminSettings); home.emit_new_queue_data(); respond_success(req, res, `Rejoin time updated successfully to ${rejoinTime} minutes`); } From d052ba7bbc23920fea6fb50277fe7c96b2ba67e8 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 26 Dec 2023 10:59:44 -0500 Subject: [PATCH 02/26] add adminSettings.json to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0c30ee3..7b8fc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,4 @@ dist # Config Files config.json +adminSettings.json From cb0b546ba5fba2f869cdd63f4b437c01ad9ebbbf Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 26 Dec 2023 11:00:08 -0500 Subject: [PATCH 03/26] fix queue rejoin field on settings page --- .../settings/admin/QueueRejoinSettings.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/client/src/components/settings/admin/QueueRejoinSettings.tsx b/client/src/components/settings/admin/QueueRejoinSettings.tsx index 75caa64..a79247c 100644 --- a/client/src/components/settings/admin/QueueRejoinSettings.tsx +++ b/client/src/components/settings/admin/QueueRejoinSettings.tsx @@ -1,5 +1,5 @@ -import React, {useContext} from 'react'; +import React, {useContext, useEffect, useState} from 'react'; import { Button, CardContent, Typography, TextField, Grid, } from '@mui/material'; @@ -11,18 +11,23 @@ import {QueueDataContext} from '../../../contexts/QueueDataContext'; export default function QueueRejoinSettings(props) { const {queueData} = useContext(QueueDataContext); + + const [rejoinTime, setRejoinTime] = useState(15); + + useEffect(() => { + setRejoinTime(queueData.rejoinTime); + }, [queueData]); + const onSubmit = (event) => { event.preventDefault(); SettingsService.updateRejoinTime( JSON.stringify({ - rejoinTime: rejoinTimeInput, + rejoinTime: rejoinTime, }), ); }; - let rejoinTimeInput = queueData.rejoinTime; - return ( @@ -39,9 +44,9 @@ export default function QueueRejoinSettings(props) { variant="standard" sx={{mx: 1, mt: -1}} style={{width: '50px'}} - defaultValue={queueData.rejoinTime ? queueData.rejoinTime : 10} + value={rejoinTime} onChange={(e) => { - rejoinTimeInput = parseInt(e.target.value, 10); + setRejoinTime(parseInt(e.target.value, 10)); }} inputProps={{min: 0, style: {textAlign: 'center'}}} /> From 4ebed9d20983acc55293559919eba73fd223541d Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 26 Dec 2023 12:05:41 -0500 Subject: [PATCH 04/26] Include locations in saved admin settings --- server/controllers/settings.js | 74 +++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/server/controllers/settings.js b/server/controllers/settings.js index c3ba35c..9cd700c 100644 --- a/server/controllers/settings.js +++ b/server/controllers/settings.js @@ -16,7 +16,8 @@ let adminSettings = { currSem: "F23", slackURL: null, questionsURL: '', - rejoinTime: 15 + rejoinTime: 15, + dayDictionary: {} }; // If no admin setting have been generated, use the above default values if (!fs.existsSync('../adminSettings.json')) { @@ -24,7 +25,7 @@ if (!fs.existsSync('../adminSettings.json')) { fs.writeFile('../adminSettings.json', json, 'utf8', function () { console.log('Created admin settings JSON'); }); -} +} exports.get_admin_settings = function () { @@ -37,7 +38,8 @@ exports.get_admin_settings = function () { currSem: "F23", slackURL: null, questionsURL: '', - rejoinTime: 15 + rejoinTime: 15, + dayDictionary: {} }; } @@ -747,32 +749,35 @@ exports.post_upload_ta_csv = function (req, res) { } /* BEGIN LOCATIONS */ -let dayDictionary = {} // invariant: rooms are held at -1 to make sure they appear in the options, but could be empty days // when removing a room, need to remove it from -1 as well // mapping is 1-to-1 where Sunday = 0... Saturday = 6 const dayToRoomDictionary = (obj) => { - return Object.entries(obj).reduce((ret, entry) => { - const [key, rooms] = entry; - for (let roomIdx in rooms) { - let room = rooms[roomIdx] - if (ret[room]) { - // seen before - let keyInt = parseInt(key) - if (keyInt != null) { - ret[room].push(keyInt) - } - } else { - let keyInt = parseInt(key) - if (keyInt != null) { - ret[room] = [keyInt] + if (obj) { + return Object.entries(obj).reduce((ret, entry) => { + const [key, rooms] = entry; + for (let roomIdx in rooms) { + let room = rooms[roomIdx] + if (ret[room]) { + // seen before + let keyInt = parseInt(key) + if (keyInt != null) { + ret[room].push(keyInt) + } + } else { + let keyInt = parseInt(key) + if (keyInt != null) { + ret[room] = [keyInt] + } } } - } - return ret; - }, {}) + return ret; + }, {}) + } else { + return {} + } } exports.add_location = function (req, res) { @@ -787,12 +792,13 @@ exports.add_location = function (req, res) { return; } - if (dayDictionary["-1"]) { - dayDictionary["-1"].push(room) + if (adminSettings.dayDictionary["-1"]) { + adminSettings.dayDictionary["-1"].push(room) } else { - dayDictionary["-1"] = [room] + adminSettings.dayDictionary["-1"] = [room] } + writeAdminSettings(adminSettings); home.emit_new_queue_data(); respond_success(req, res, `Location added successfully`); } @@ -811,7 +817,7 @@ exports.post_update_locations = function (req, res) { return; } - var newDayDictionary = dayDictionary + var newDayDictionary = adminSettings.dayDictionary for (var day in daysOfWeek) { if (days.includes(daysOfWeek[day])) { // day is selected for room @@ -834,16 +840,17 @@ exports.post_update_locations = function (req, res) { } } } - dayDictionary = newDayDictionary + adminSettings.dayDictionary = newDayDictionary + writeAdminSettings(adminSettings); home.emit_new_queue_data(); respond_success(req, res, `Location changed successfully`); } exports.internal_get_locations = function () { return { - dayDictionary: dayDictionary, - roomDictionary: dayToRoomDictionary(dayDictionary) + dayDictionary: adminSettings.dayDictionary, + roomDictionary: dayToRoomDictionary(adminSettings.dayDictionary) } } @@ -863,25 +870,26 @@ exports.remove_location = function (req, res) { for (dayIdx in days) { if (dayIdx && dayIdx != null) { let dayInt = days[dayIdx] - if (!dayDictionary[dayInt].includes(room)) { + if (!adminSettings.dayDictionary[dayInt].includes(room)) { console.log("hmm shouldn't really have a day selected for this room") } else { - let roomArrForDay = dayDictionary[dayInt] + let roomArrForDay = adminSettings.dayDictionary[dayInt] // safe because room is in roomArrForDay so idx >= 0 roomArrForDay.splice(roomArrForDay.indexOf(room), 1) - dayDictionary[dayInt] = roomArrForDay + adminSettings.dayDictionary[dayInt] = roomArrForDay } } } // REMOVE ROOM FROM -1!! - let emptyRoomArr = dayDictionary["-1"] + let emptyRoomArr = adminSettings.dayDictionary["-1"] let idx = emptyRoomArr.indexOf(room) if (idx >= 0) { emptyRoomArr.splice(idx, 1) } - dayDictionary["-1"] = emptyRoomArr + adminSettings.dayDictionary["-1"] = emptyRoomArr + writeAdminSettings(adminSettings); home.emit_new_queue_data(); respond_success(req, res, `Location removed successfully`); } From 9d52bf55072feee9b5e233df3fca321c226fd2b8 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 26 Dec 2023 12:24:15 -0500 Subject: [PATCH 05/26] respond success works for TAs and Students --- server/controllers/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controllers/settings.js b/server/controllers/settings.js index 1551df9..fba5825 100644 --- a/server/controllers/settings.js +++ b/server/controllers/settings.js @@ -42,7 +42,7 @@ function respond(req, res, message, data, status) { } function respond_success(req, res, message = null) { - if (!req.user || !req.user.isTA) { + if (!req.user) { respond_error(req, res, "You don't have permissions to view this page", 404); return; } From 7aecdc1813d5322ccb2d33fb6e7cb74f4a50b087 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 26 Dec 2023 12:37:08 -0500 Subject: [PATCH 06/26] Show change name button on mobile view --- client/src/components/navbar/ChangeNameBtn.tsx | 16 +++++++++++----- client/src/components/navbar/Navbar.tsx | 5 ++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/components/navbar/ChangeNameBtn.tsx b/client/src/components/navbar/ChangeNameBtn.tsx index 874f55d..4eac7b4 100644 --- a/client/src/components/navbar/ChangeNameBtn.tsx +++ b/client/src/components/navbar/ChangeNameBtn.tsx @@ -1,13 +1,13 @@ import React, {useState} from 'react'; import { - Button, Dialog, DialogActions, DialogTitle, DialogContent, DialogContentText, TextField, + Button, Dialog, DialogActions, DialogTitle, DialogContent, DialogContentText, TextField, Typography, MenuItem, } from '@mui/material'; import EditIcon from '@mui/icons-material/Edit'; import SettingsService from '../../services/SettingsService'; export default function ChangeNameBtn(props) { - const {setpname, pname} = props; + const {setpname, pname, mobile} = props; const [tmpPrefName, setTmpPrefName] = useState(pname); const [open, setOpen] = useState(false); @@ -28,9 +28,15 @@ export default function ChangeNameBtn(props) { return (
- + { + mobile ? + + Change Name + : + + }
Change Name diff --git a/client/src/components/navbar/Navbar.tsx b/client/src/components/navbar/Navbar.tsx index 71cfe6a..7fa37ad 100644 --- a/client/src/components/navbar/Navbar.tsx +++ b/client/src/components/navbar/Navbar.tsx @@ -164,6 +164,9 @@ export default function Navbar(props) { } + { + userData.isAuthenticated && + } } @@ -199,7 +202,7 @@ export default function Navbar(props) { { - userData.isAuthenticated && + userData.isAuthenticated && } From c1bd90fb96c7fc83f31c6798de8b3d5f5e4f825d Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 26 Dec 2023 12:38:22 -0500 Subject: [PATCH 07/26] Show change name before logout in mobile --- client/src/components/navbar/Navbar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/navbar/Navbar.tsx b/client/src/components/navbar/Navbar.tsx index 7fa37ad..a03ba7e 100644 --- a/client/src/components/navbar/Navbar.tsx +++ b/client/src/components/navbar/Navbar.tsx @@ -156,6 +156,9 @@ export default function Navbar(props) { )) } + { + userData.isAuthenticated && + } { userData.isAuthenticated && @@ -164,9 +167,6 @@ export default function Navbar(props) { } - { - userData.isAuthenticated && - } } From 5dab7502d8f901e9430ad3169d7313824b5dc981 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 15:14:43 -0500 Subject: [PATCH 08/26] Add chart.js --- client/package-lock.json | 57 ++++++++++++++++++++++++++++++++++++++++ client/package.json | 3 +++ 2 files changed, 60 insertions(+) diff --git a/client/package-lock.json b/client/package-lock.json index 2f5d067..b9c6456 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -25,11 +25,14 @@ "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "axios": "^0.27.2", + "chart.js": "^4.4.1", + "chat.js": "^1.0.2", "downloadjs": "^1.4.7", "luxon": "^2.4.0", "material-react-toastify": "^1.0.1", "number-to-words": "^1.2.4", "react": "^18.1.0", + "react-chartjs-2": "^5.2.0", "react-cookie": "^4.1.1", "react-dom": "^18.1.0", "react-router-dom": "^6.3.0", @@ -3176,6 +3179,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -6474,6 +6482,22 @@ "node": ">=6" } }, + "node_modules/chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, + "node_modules/chat.js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chat.js/-/chat.js-1.0.2.tgz", + "integrity": "sha512-0Q0FnNyFc+xiZoe2dexBbHBDf2F9CSt2bpBgjZ8gv4yghgTrv+G/EB9nsUDdt+lA0F/IOb+PlHPoky1SHDMtqQ==" + }, "node_modules/check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -14951,6 +14975,15 @@ "node": ">=14" } }, + "node_modules/react-chartjs-2": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-cookie": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", @@ -20315,6 +20348,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -22603,6 +22641,19 @@ "resolved": "https://registry.npmjs.org/charcodes/-/charcodes-0.2.0.tgz", "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==" }, + "chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, + "chat.js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chat.js/-/chat.js-1.0.2.tgz", + "integrity": "sha512-0Q0FnNyFc+xiZoe2dexBbHBDf2F9CSt2bpBgjZ8gv4yghgTrv+G/EB9nsUDdt+lA0F/IOb+PlHPoky1SHDMtqQ==" + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -28598,6 +28649,12 @@ "whatwg-fetch": "^3.6.2" } }, + "react-chartjs-2": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "requires": {} + }, "react-cookie": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", diff --git a/client/package.json b/client/package.json index fc7b4e4..cacd2a3 100644 --- a/client/package.json +++ b/client/package.json @@ -20,11 +20,14 @@ "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "axios": "^0.27.2", + "chart.js": "^4.4.1", + "chat.js": "^1.0.2", "downloadjs": "^1.4.7", "luxon": "^2.4.0", "material-react-toastify": "^1.0.1", "number-to-words": "^1.2.4", "react": "^18.1.0", + "react-chartjs-2": "^5.2.0", "react-cookie": "^4.1.1", "react-dom": "^18.1.0", "react-router-dom": "^6.3.0", From 06eb4ef240312396fe6a63dcc8ae1bb84a6ca37e Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 15:15:00 -0500 Subject: [PATCH 09/26] Make a bit larger --- client/src/components/metrics/PersonalStats.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/metrics/PersonalStats.tsx b/client/src/components/metrics/PersonalStats.tsx index f2f205d..6cdc76c 100644 --- a/client/src/components/metrics/PersonalStats.tsx +++ b/client/src/components/metrics/PersonalStats.tsx @@ -90,7 +90,7 @@ export default function PersonalStats() { {Number(averageHelpTime).toFixed(2)} - + From 5222f84997f0953990522dbe11d95d65966e1e38 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 15:15:14 -0500 Subject: [PATCH 10/26] Fix numStudentsPerDayLastWeek --- client/src/components/metrics/Graph.tsx | 90 ++++++++++++++++++------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/client/src/components/metrics/Graph.tsx b/client/src/components/metrics/Graph.tsx index 6366d58..2293fb2 100644 --- a/client/src/components/metrics/Graph.tsx +++ b/client/src/components/metrics/Graph.tsx @@ -6,6 +6,10 @@ import { import {DateTime} from 'luxon'; import {ResponsiveContainer, LineChart, BarChart, Bar, Line, Label, XAxis, YAxis, CartesianGrid, Tooltip} from 'recharts'; +import {Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip as Tooltip2, Legend} from 'chart.js'; +ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip2, Legend); +import {Line as Line2} from 'react-chartjs-2'; + import MetricsService from '../../services/MetricsService'; export default function Graph() { @@ -37,30 +41,68 @@ export default function Graph() { Number of Students per Day (in the last week) - - - - - - - - - - - + dateFormatter(day.day)), + datasets: [ + { + label: 'Number of Students', + data: numStudentsPerDayLastWeek.map((day) => day.students), + fill: false, + backgroundColor: theme.palette.primary.main, + borderColor: theme.palette.primary.main, + borderWidth: 3, + tension: 0.3, + }, + ], + }} + /> Number of Students per Day (overall) From 620bbcd6ea3bd2dce2e18cab87e560c1ab6decfc Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 15:15:44 -0500 Subject: [PATCH 11/26] Small server-side potential fix for number of students per day last week, should check this fix on the live DB --- server/controllers/metrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index ac86877..981ead0 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -299,7 +299,7 @@ exports.get_num_students_per_day_last_week = (req, res) => { ], where: { entry_time: { - [Sequelize.Op.gte]: today - 7 * 24 * 60 * 60 * 1000, + [Sequelize.Op.gte]: new Date(today - 7 * 24 * 60 * 60 * 1000), } }, group: [Sequelize.fn('date', Sequelize.col('entry_time'))], From 6c4b9c6c02588829a18cb2f615f74c4a81c2debe Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 16:13:34 -0500 Subject: [PATCH 12/26] Update metrics queries to work correctly and use charts.js instead of recharts --- client/package-lock.json | 414 ------------------------ client/package.json | 1 - client/src/components/metrics/Graph.tsx | 176 +++++++--- server/controllers/metrics.js | 7 +- 4 files changed, 129 insertions(+), 469 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index b9c6456..15c4ceb 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -38,7 +38,6 @@ "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-window": "^1.8.7", - "recharts": "^2.1.13", "socket.io-client": "^4.5.1", "styled-components": "^5.3.5", "typescript": "^4.8.4", @@ -6558,11 +6557,6 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" }, - "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "node_modules/clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -7135,11 +7129,6 @@ "node": ">=0.10.0" } }, - "node_modules/css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -7319,100 +7308,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, - "node_modules/d3-array": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", - "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", - "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7452,11 +7347,6 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" - }, "node_modules/decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -8870,11 +8760,6 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/fast-equals": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", - "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" - }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -10030,14 +9915,6 @@ "node": ">= 0.4" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "engines": { - "node": ">=12" - } - }, "node_modules/ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -15136,11 +15013,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -15149,18 +15021,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-resize-detector": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", - "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-router": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", @@ -15265,43 +15125,6 @@ "node": ">=10" } }, - "node_modules/react-smooth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", - "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", - "dependencies": { - "fast-equals": "^2.0.0", - "react-transition-group": "2.9.0" - }, - "peerDependencies": { - "prop-types": "^15.6.0", - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-smooth/node_modules/dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "dependencies": { - "@babel/runtime": "^7.1.2" - } - }, - "node_modules/react-smooth/node_modules/react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "dependencies": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", @@ -15357,45 +15180,6 @@ "node": ">=8.10.0" } }, - "node_modules/recharts": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.13.tgz", - "integrity": "sha512-9VWu2nzExmfiMFDHKqRFhYlJVmjzQGVKH5rBetXR4EuyEXuu3Y6cVxQuNEdusHhbm4SoPPrVDCwlBdREL3sQPA==", - "dependencies": { - "classnames": "^2.2.5", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "eventemitter3": "^4.0.1", - "lodash": "^4.17.19", - "react-is": "^16.10.2", - "react-resize-detector": "^7.1.2", - "react-smooth": "^2.0.1", - "recharts-scale": "^0.4.4", - "reduce-css-calc": "^2.1.8" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "prop-types": "^15.6.0", - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/recharts-scale": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", - "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", - "dependencies": { - "decimal.js-light": "^2.4.1" - } - }, - "node_modules/recharts/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -15430,20 +15214,6 @@ "node": ">=8" } }, - "node_modules/reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "dependencies": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - } - }, - "node_modules/reduce-css-calc/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -22699,11 +22469,6 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, "clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -23116,11 +22881,6 @@ } } }, - "css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" - }, "css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -23247,73 +23007,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, - "d3-array": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", - "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", - "requires": { - "internmap": "1 - 2" - } - }, - "d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" - }, - "d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" - }, - "d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "requires": { - "d3-color": "1 - 3" - } - }, - "d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" - }, - "d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "requires": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - } - }, - "d3-shape": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", - "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", - "requires": { - "d3-path": "1 - 3" - } - }, - "d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", - "requires": { - "d3-array": "2 - 3" - } - }, - "d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "requires": { - "d3-time": "1 - 3" - } - }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -23342,11 +23035,6 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" }, - "decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" - }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -24382,11 +24070,6 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-equals": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", - "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" - }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -25221,11 +24904,6 @@ "side-channel": "^1.0.4" } }, - "internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" - }, "ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -28770,24 +28448,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, - "react-resize-detector": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", - "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", - "requires": { - "lodash": "^4.17.21" - } - }, "react-router": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", @@ -28867,36 +28532,6 @@ } } }, - "react-smooth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", - "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", - "requires": { - "fast-equals": "^2.0.0", - "react-transition-group": "2.9.0" - }, - "dependencies": { - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "requires": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - } - } - } - }, "react-transition-group": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", @@ -28935,39 +28570,6 @@ "picomatch": "^2.2.1" } }, - "recharts": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.13.tgz", - "integrity": "sha512-9VWu2nzExmfiMFDHKqRFhYlJVmjzQGVKH5rBetXR4EuyEXuu3Y6cVxQuNEdusHhbm4SoPPrVDCwlBdREL3sQPA==", - "requires": { - "classnames": "^2.2.5", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "eventemitter3": "^4.0.1", - "lodash": "^4.17.19", - "react-is": "^16.10.2", - "react-resize-detector": "^7.1.2", - "react-smooth": "^2.0.1", - "recharts-scale": "^0.4.4", - "reduce-css-calc": "^2.1.8" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "recharts-scale": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", - "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", - "requires": { - "decimal.js-light": "^2.4.1" - } - }, "recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -28995,22 +28597,6 @@ "strip-indent": "^3.0.0" } }, - "reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "requires": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/client/package.json b/client/package.json index cacd2a3..8933246 100644 --- a/client/package.json +++ b/client/package.json @@ -33,7 +33,6 @@ "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-window": "^1.8.7", - "recharts": "^2.1.13", "socket.io-client": "^4.5.1", "styled-components": "^5.3.5", "typescript": "^4.8.4", diff --git a/client/src/components/metrics/Graph.tsx b/client/src/components/metrics/Graph.tsx index 2293fb2..c921bde 100644 --- a/client/src/components/metrics/Graph.tsx +++ b/client/src/components/metrics/Graph.tsx @@ -4,11 +4,10 @@ import { } from '@mui/material'; import {DateTime} from 'luxon'; -import {ResponsiveContainer, LineChart, BarChart, Bar, Line, Label, XAxis, YAxis, CartesianGrid, Tooltip} from 'recharts'; -import {Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip as Tooltip2, Legend} from 'chart.js'; -ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip2, Legend); -import {Line as Line2} from 'react-chartjs-2'; +import {Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title, Tooltip as Tooltip2, Legend} from 'chart.js'; +ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title, Tooltip2, Legend); +import {Line, Bar} from 'react-chartjs-2'; import MetricsService from '../../services/MetricsService'; @@ -41,7 +40,7 @@ export default function Graph() { Number of Students per Day (in the last week) - Number of Students per Day (overall) - - - - - - - - - - - + dateFormatter(day.day)), + datasets: [ + { + label: 'Number of Students', + data: numStudentsOverall.map((day) => day.students), + fill: false, + backgroundColor: theme.palette.primary.main, + borderColor: theme.palette.primary.main, + borderWidth: 3, + tension: 0.3, + }, + ], + }} + /> Number of Students per Day of the Week - - - - - - - + day.day), + datasets: [ + { + label: 'Number of Students', + data: numStudentsPerDay.map((day) => day.students), + backgroundColor: theme.palette.primary.main, + borderColor: theme.palette.primary.main, + borderWidth: 3, + }, + ], + }} + /> ); } diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index 981ead0..c3ea6ba 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -325,17 +325,18 @@ exports.get_num_students_per_day = (req, res) => { models.question.findAll({ attributes: [ - [Sequelize.fn('date_trunc', 'day', Sequelize.col('entry_time')), 'day_of_week'], + [Sequelize.fn('date_part', 'dow', Sequelize.col('entry_time')), 'day_of_week'], [Sequelize.fn('count', Sequelize.col('question_id')), 'count'] ], - group: [Sequelize.fn('date_trunc', 'day', Sequelize.col('entry_time')), 'day_of_week'], + group: [Sequelize.fn('date_part', 'dow', Sequelize.col('entry_time'))], order: [[Sequelize.col('count'), 'DESC']] }).then((data) => { let numStudentsPerDay = []; + const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; for (const row of data) { let datecount = row.dataValues; - datecount.day_of_week = new Date(datecount.day_of_week).toLocaleDateString('en-US', {weekday : 'long'}); + datecount.day_of_week = days[datecount.day_of_week]; numStudentsPerDay.push({'day': datecount.day_of_week, 'students': datecount.count}); } From bd1be49f08d3f82ab846a3b586f222c75f6941c8 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 20:19:25 -0500 Subject: [PATCH 13/26] Sort by day of week --- client/src/components/metrics/Graph.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/components/metrics/Graph.tsx b/client/src/components/metrics/Graph.tsx index c921bde..4316c63 100644 --- a/client/src/components/metrics/Graph.tsx +++ b/client/src/components/metrics/Graph.tsx @@ -23,7 +23,14 @@ export default function Graph() { }); MetricsService.getNumStudentsPerDay().then((res) => { - setNumStudentsPerDay(res.data.numStudentsPerDay); + const dataBack = res.data.numStudentsPerDay; + // sort by day of week + dataBack.sort((a, b) => { + const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + return days.indexOf(a.day) - days.indexOf(b.day); + }); + + setNumStudentsPerDay(dataBack); }); MetricsService.getNumStudentsOverall().then((res) => { From 8d5fff769f0ea20efb471160d9848cc1d54500bf Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 20:27:33 -0500 Subject: [PATCH 14/26] Use date objects and 8hr windows --- server/controllers/metrics.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index c3ea6ba..e4b0d49 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -119,7 +119,7 @@ exports.get_num_questions_today = (req, res) => { models.question.findAndCountAll({ where: { entry_time: { - [Sequelize.Op.gte]: today - 4 * 60 * 60 * 1000, + [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), } } }).then(({count}) => { @@ -137,7 +137,7 @@ exports.get_num_bad_questions_today = (req, res) => { models.question.findAndCountAll({ where: { entry_time: { - [Sequelize.Op.gte]: today - 4 * 60 * 60 * 1000, + [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), }, num_asked_to_fix: { [Sequelize.Op.gt]: 0 @@ -157,7 +157,7 @@ exports.get_avg_wait_time_today = (req, res) => { models.question.findAndCountAll({ where: { entry_time: { - [Sequelize.Op.gte]: today - 4 * 60 * 60 * 1000, + [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), }, help_time: { [Sequelize.Op.ne]: null, @@ -187,7 +187,7 @@ exports.get_ta_student_ratio_today = (req, res) => { models.question.findAndCountAll({ where: { entry_time: { - [Sequelize.Op.gte]: today - 4 * 60 * 60 * 1000, + [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), } } }).then(({count, rows}) => { From 7db1879479110a96b7aec4ddce4ee026548a7eec Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 21:12:20 -0500 Subject: [PATCH 15/26] Account for EST time zone in metrics calculations --- server/controllers/metrics.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index e4b0d49..25b4038 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -294,7 +294,7 @@ exports.get_num_students_per_day_last_week = (req, res) => { models.question.findAll({ attributes: [ - [Sequelize.fn('date', Sequelize.col('entry_time')), 'day'], + [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`)), 'day'], [Sequelize.fn('count', Sequelize.col('question_id')), 'count'] ], where: { @@ -302,7 +302,7 @@ exports.get_num_students_per_day_last_week = (req, res) => { [Sequelize.Op.gte]: new Date(today - 7 * 24 * 60 * 60 * 1000), } }, - group: [Sequelize.fn('date', Sequelize.col('entry_time'))], + group: [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`))], order: [[Sequelize.col('day'), 'ASC']] }).then((data) => { let numStudentsPerDayLastWeek = []; @@ -325,10 +325,10 @@ exports.get_num_students_per_day = (req, res) => { models.question.findAll({ attributes: [ - [Sequelize.fn('date_part', 'dow', Sequelize.col('entry_time')), 'day_of_week'], + [Sequelize.fn('date_part', 'dow', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`)), 'day_of_week'], [Sequelize.fn('count', Sequelize.col('question_id')), 'count'] ], - group: [Sequelize.fn('date_part', 'dow', Sequelize.col('entry_time'))], + group: [Sequelize.fn('date_part', 'dow', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`))], order: [[Sequelize.col('count'), 'DESC']] }).then((data) => { let numStudentsPerDay = []; @@ -352,10 +352,10 @@ exports.get_num_students_overall = (req, res) => { models.question.findAll({ attributes: [ - [Sequelize.fn('date', Sequelize.col('entry_time')), 'day'], + [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`)), 'day'], [Sequelize.fn('count', Sequelize.col('question_id')), 'count'] ], - group: [Sequelize.fn('date', Sequelize.col('entry_time'))], + group: [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`))], order: [[Sequelize.col('day'), 'ASC']] }).then((data) => { let numStudentsOverall = []; From f484612ac75fa76aab371b2a8b8e9c78709459df Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 21:23:26 -0500 Subject: [PATCH 16/26] prevent vertical growth on graphs when resizing page --- client/src/components/metrics/Graph.tsx | 333 ++++++++++++------------ 1 file changed, 171 insertions(+), 162 deletions(-) diff --git a/client/src/components/metrics/Graph.tsx b/client/src/components/metrics/Graph.tsx index 4316c63..f8a3454 100644 --- a/client/src/components/metrics/Graph.tsx +++ b/client/src/components/metrics/Graph.tsx @@ -47,197 +47,206 @@ export default function Graph() { Number of Students per Day (in the last week) - + dateFormatter(day.day)), - datasets: [ - { - label: 'Number of Students', - data: numStudentsPerDayLastWeek.map((day) => day.students), - fill: false, - backgroundColor: theme.palette.primary.main, - borderColor: theme.palette.primary.main, - borderWidth: 3, - tension: 0.3, - }, - ], - }} - /> + }} + + data={{ + labels: numStudentsPerDayLastWeek.map((day) => dateFormatter(day.day)), + datasets: [ + { + label: 'Number of Students', + data: numStudentsPerDayLastWeek.map((day) => day.students), + fill: false, + backgroundColor: theme.palette.primary.main, + borderColor: theme.palette.primary.main, + borderWidth: 3, + tension: 0.3, + }, + ], + }} + /> + Number of Students per Day (overall) - + dateFormatter(day.day)), - datasets: [ - { - label: 'Number of Students', - data: numStudentsOverall.map((day) => day.students), - fill: false, - backgroundColor: theme.palette.primary.main, - borderColor: theme.palette.primary.main, - borderWidth: 3, - tension: 0.3, - }, - ], - }} - /> + }} + + data={{ + labels: numStudentsOverall.map((day) => dateFormatter(day.day)), + datasets: [ + { + label: 'Number of Students', + data: numStudentsOverall.map((day) => day.students), + fill: false, + backgroundColor: theme.palette.primary.main, + borderColor: theme.palette.primary.main, + borderWidth: 3, + tension: 0.3, + }, + ], + }} + /> + Number of Students per Day of the Week - + day.day), - datasets: [ - { - label: 'Number of Students', - data: numStudentsPerDay.map((day) => day.students), - backgroundColor: theme.palette.primary.main, - borderColor: theme.palette.primary.main, - borderWidth: 3, - }, - ], - }} - /> + }} + data={{ + labels: numStudentsPerDay.map((day) => day.day), + datasets: [ + { + label: 'Number of Students', + data: numStudentsPerDay.map((day) => day.students), + backgroundColor: theme.palette.primary.main, + borderColor: theme.palette.primary.main, + borderWidth: 3, + }, + ], + }} + /> + ); } From 60a0ddfc1ddfa105ed458d0257f15e4e9215fa9a Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Tue, 9 Jan 2024 21:44:54 -0500 Subject: [PATCH 17/26] Change default semester to S23 because of legacy reasons lol --- server/controllers/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/controllers/settings.js b/server/controllers/settings.js index 9cd700c..180e207 100644 --- a/server/controllers/settings.js +++ b/server/controllers/settings.js @@ -13,7 +13,7 @@ const home = require('./home'); // In production, these should be cleared var fs = require('fs'); let adminSettings = { - currSem: "F23", + currSem: "S23", slackURL: null, questionsURL: '', rejoinTime: 15, @@ -35,7 +35,7 @@ exports.get_admin_settings = function () { } else { console.log('No admin settings found'); adminSettings = { - currSem: "F23", + currSem: "S23", slackURL: null, questionsURL: '', rejoinTime: 15, From a767ed6dba0287e7361f0089f09e6459bf8aa730 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Fri, 12 Jan 2024 23:43:02 -0500 Subject: [PATCH 18/26] Add ranked students and TAs to metrics for admins --- .../src/components/metrics/AdminMetrics.tsx | 181 ++++++++++++++++++ client/src/components/metrics/MetricsMain.tsx | 10 +- client/src/services/MetricsService.tsx | 6 + server/controllers/metrics.js | 129 +++++++++++++ server/routes/metrics.js | 2 + 5 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 client/src/components/metrics/AdminMetrics.tsx diff --git a/client/src/components/metrics/AdminMetrics.tsx b/client/src/components/metrics/AdminMetrics.tsx new file mode 100644 index 0000000..9e6cf4b --- /dev/null +++ b/client/src/components/metrics/AdminMetrics.tsx @@ -0,0 +1,181 @@ +import React, {useEffect, useState} from 'react'; +import MetricsService from '../../services/MetricsService'; +import { + Card, Divider, Typography, Stack, Table, TableBody, TableCell, + TableContainer, TableHead, TablePagination, TableRow, +} from '@mui/material'; + +export default function AdminMetrics() { + const [rankedStudents, setRankedStudents] = useState([]); + const [rankedTAs, setRankedTAs] = useState([]); + + // students pagination + const [studentPage, setStudentPage] = useState(0); + const [rowsPerStudentPage, setRowsPerStudentPage] = useState(10); + const handleChangeStudentPage = (event, newPage) => { + setStudentPage(newPage); + }; + const handleChangeRowsPerStudentPage = (event) => { + setRowsPerStudentPage(+event.target.value); + setStudentPage(0); + }; + + // tas pagination + const [taPage, setTAPage] = useState(0); + const [rowsPerTAPage, setRowsPerTAPage] = useState(10); + const handleChangeTAPage = (event, newPage) => { + setTAPage(newPage); + }; + const handleChangeRowsPerTAPage = (event) => { + setRowsPerTAPage(+event.target.value); + setTAPage(0); + }; + + useEffect(() => { + MetricsService.getRankedStudents().then((res) => { + setRankedStudents(res.data.rankedStudents.map((student) => { + return { + ...student, + average: Math.round(student.timeHelped / student.count * 10) / 10, + }; + })); + }); + + MetricsService.getRankedTAs().then((res) => { + setRankedTAs(res.data.rankedTAs.map((ta) => { + return { + ...ta, + average: Math.round(ta.timeHelping / ta.count * 10) / 10, + }; + })); + }); + }, []); + + const studentCols = [ + {id: 'student_andrew', label: 'Andrew ID', width: 25}, + {id: 'student_name', label: 'Name', width: 25}, + {id: 'count', label: 'Total Questions', width: 100}, + {id: 'timeHelped', label: 'Total Helping Time (min)', width: 100}, + {id: 'average', label: 'Average Helping Time (min)', width: 100}, + ]; + + const taCols = [ + {id: 'ta_andrew', label: 'Andrew ID', width: 25}, + {id: 'ta_name', label: 'Name', width: 25}, + {id: 'count', label: 'Total Questions Answered', width: 100}, + {id: 'timeHelping', label: 'Total Time Helping (min)', width: 100}, + {id: 'average', label: 'Average Time Helping (min)', width: 100}, + ]; + + return ( +
+ + Ranked Students and Ranked TAs + + + + } + spacing={2} + sx={{m: 2}} + > + + +
+ + + {studentCols.map((column) => ( + + {column.label} + + ))} + + + + {rankedStudents + .slice(studentPage * rowsPerStudentPage, studentPage * rowsPerStudentPage + rowsPerStudentPage) + .map((row) => { + return ( + + {studentCols.map((column) => { + const value = row[column.id]; + return ( + + {value} + + ); + })} + + ); + }) + } + +
+
+ +
+ + + + + + {taCols.map((column) => ( + + {column.label} + + ))} + + + + {rankedTAs + .slice(taPage * rowsPerTAPage, taPage * rowsPerTAPage + rowsPerTAPage) + .map((row) => { + return ( + + {taCols.map((column) => { + const value = row[column.id]; + return ( + + {value} + + ); + })} + + ); + }) + } + +
+
+ +
+ + +
+ ); +} diff --git a/client/src/components/metrics/MetricsMain.tsx b/client/src/components/metrics/MetricsMain.tsx index 0aa3898..bff88ea 100644 --- a/client/src/components/metrics/MetricsMain.tsx +++ b/client/src/components/metrics/MetricsMain.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useContext} from 'react'; import { Typography, } from '@mui/material'; @@ -8,8 +8,12 @@ import PersonalStats from './PersonalStats'; import OverallStats from './OverallStats'; import CumulativeStats from './CumulativeStats'; import Graph from './Graph'; +import {UserDataContext} from '../../contexts/UserDataContext'; +import AdminMetrics from './AdminMetrics'; export default function MetricsMain(props) { + const {userData} = useContext(UserDataContext); + return (
@@ -21,6 +25,10 @@ export default function MetricsMain(props) { + + { + userData.isAdmin && + }
); } diff --git a/client/src/services/MetricsService.tsx b/client/src/services/MetricsService.tsx index 3fc323d..07f65e8 100644 --- a/client/src/services/MetricsService.tsx +++ b/client/src/services/MetricsService.tsx @@ -1,6 +1,12 @@ import http from '../http-common'; class MetricsDataService { + getRankedTAs() { + return http.get('/metrics/rankedTAs'); + } + getRankedStudents() { + return http.get('/metrics/rankedStudents'); + } getHelpedStudents() { return http.get('/metrics/helpedStudents'); } diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index 25b4038..bb2dc2f 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -1,6 +1,7 @@ const Sequelize = require('sequelize'); const Promise = require("bluebird"); const moment = require("moment-timezone"); +let settings = require('./settings'); const models = require('../models'); const { sequelize } = require('../models'); @@ -368,3 +369,131 @@ exports.get_num_students_overall = (req, res) => { respond(req, res, "Got number of students overall", { numStudentsOverall: numStudentsOverall }, 200); }); } + +exports.get_ranked_students = (req, res) => { + if (!req.user || !req.user.isTA || !req.user.isAdmin) { + respond_error(req, res, "You don't have permission to perform this operation", 403); + return; + } + + let studentMap = {}; + models.question.findAll({ + where: { + sem_id: settings.get_admin_settings().currSem, + help_time: { + [Sequelize.Op.ne]: null + } + } + }).then((questionModels) => { + + for (const questionModel of questionModels) { + let question = questionModel.dataValues; + + if (question.student_id in studentMap) { + studentMap[question.student_id].count++; + studentMap[question.student_id].timeHelped += (question.exit_time - question.help_time) / 1000 / 60; + } else { + studentMap[question.student_id] = { + count: 1, + timeHelped: (question.exit_time - question.help_time) / 1000 / 60, + }; + } + } + + let accountReqs = []; + for (const student_id in studentMap) { + accountReqs.push(models.account.findByPk(student_id)); + } + + return Promise.all(accountReqs); + }).then((accounts) => { + let rankedStudents = []; + + for (const account of accounts) { + let accountData = account.dataValues; + let user_id = accountData.user_id; + + if (user_id in studentMap) { + rankedStudents.push({ + student_name: accountData.preferred_name, + student_andrew: accountData.email.split("@")[0], + count: studentMap[user_id].count, + timeHelped: Math.round(studentMap[user_id].timeHelped * 10) / 10, + }); + } + } + + rankedStudents.sort((a, b) => { + if (a.count != b.count) { + return b.count - a.count; + } else { + return b.timeHelped - a.timeHelped; + } + }); + respond(req, res, "Got ranked students", { rankedStudents: rankedStudents }, 200); + }); +} + +exports.get_ranked_tas = (req, res) => { + if (!req.user || !req.user.isTA || !req.user.isAdmin) { + respond_error(req, res, "You don't have permission to perform this operation", 403); + return; + } + + let taMap = {}; + models.question.findAll({ + where: { + sem_id: settings.get_admin_settings().currSem, + help_time: { + [Sequelize.Op.ne]: null + } + } + }).then((questionModels) => { + + for (const questionModel of questionModels) { + let question = questionModel.dataValues; + + if (question.ta_id in taMap) { + taMap[question.ta_id].count++; + taMap[question.ta_id].timeHelping += (question.exit_time - question.help_time) / 1000 / 60; + } else { + taMap[question.ta_id] = { + count: 1, + timeHelping: (question.exit_time - question.help_time) / 1000 / 60, + }; + } + } + + let accountReqs = []; + for (const ta_id in taMap) { + accountReqs.push(models.account.findByPk(ta_id)); + } + + return Promise.all(accountReqs); + }).then((accounts) => { + let rankedTAs = []; + + for (const account of accounts) { + let accountData = account.dataValues; + let user_id = accountData.user_id; + + if (user_id in taMap) { + rankedTAs.push({ + ta_name: accountData.preferred_name, + ta_andrew: accountData.email.split("@")[0], + count: taMap[user_id].count, + timeHelping: Math.round(taMap[user_id].timeHelping * 10) / 10, + }); + } + } + + rankedTAs.sort((a, b) => { + if (a.count != b.count) { + return b.count - a.count; + } else { + return b.timeHelped - a.timeHelped; + } + }); + respond(req, res, "Got ranked TAs", { rankedTAs: rankedTAs }, 200); + }); +} diff --git a/server/routes/metrics.js b/server/routes/metrics.js index 3f58280..ecec056 100644 --- a/server/routes/metrics.js +++ b/server/routes/metrics.js @@ -16,4 +16,6 @@ router.get('/totalAvgWaitTime', metrics.get_total_avg_wait_time); router.get('/numStudentsPerDayLastWeek', metrics.get_num_students_per_day_last_week); router.get('/numStudentsPerDay', metrics.get_num_students_per_day); router.get('/numStudentsOverall', metrics.get_num_students_overall); +router.get('/rankedStudents', metrics.get_ranked_students); +router.get('/rankedTAs', metrics.get_ranked_tas); module.exports = router; From 85b71bab528d1c4f7efab9390379dc3b7f74ce21 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Fri, 12 Jan 2024 23:55:28 -0500 Subject: [PATCH 19/26] Account for current semester and only look at completed questions for metrics --- server/controllers/metrics.js | 41 +++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index bb2dc2f..2f661bb 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -33,7 +33,8 @@ exports.get_helped_students = (req, res) => { ta_id: req.user.ta.ta_id, help_time: { [Sequelize.Op.ne]: null - } + }, + sem_id: settings.get_admin_settings().currSem, }, order: [['entry_time', 'DESC']] }).then((questionModels) => { @@ -77,7 +78,8 @@ exports.get_num_questions_answered = (req, res) => { ta_id: req.user.ta.ta_id, help_time: { [Sequelize.Op.ne]: null - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count, rows}) => { respond(req, res, "Got number of questions answered", { numQuestions: count }, 200); @@ -95,7 +97,8 @@ exports.get_avg_time_per_question = (req, res) => { ta_id: req.user.ta.ta_id, help_time: { [Sequelize.Op.ne]: null, - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count, rows}) => { let averageTime = 0; @@ -121,7 +124,7 @@ exports.get_num_questions_today = (req, res) => { where: { entry_time: { [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), - } + }, } }).then(({count}) => { respond(req, res, "Got number of questions today", { numQuestionsToday: count }, 200); @@ -228,6 +231,10 @@ exports.get_total_num_questions = (req, res) => { models.question.findAndCountAll({ where: { + sem_id: settings.get_admin_settings().currSem, + help_time: { + [Sequelize.Op.ne]: null + }, } }).then(({count}) => { respond(req, res, "Got number of questions answered", { numQuestions: count }, 200); @@ -244,7 +251,8 @@ exports.get_total_avg_time_per_question = (req, res) => { where: { help_time: { [Sequelize.Op.ne]: null, - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count, rows}) => { let averageTime = 0; @@ -270,7 +278,8 @@ exports.get_total_avg_wait_time = (req, res) => { where: { help_time: { [Sequelize.Op.ne]: null, - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count, rows}) => { @@ -301,7 +310,11 @@ exports.get_num_students_per_day_last_week = (req, res) => { where: { entry_time: { [Sequelize.Op.gte]: new Date(today - 7 * 24 * 60 * 60 * 1000), - } + }, + help_time: { + [Sequelize.Op.ne]: null + }, + sem_id: settings.get_admin_settings().currSem, }, group: [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`))], order: [[Sequelize.col('day'), 'ASC']] @@ -329,6 +342,12 @@ exports.get_num_students_per_day = (req, res) => { [Sequelize.fn('date_part', 'dow', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`)), 'day_of_week'], [Sequelize.fn('count', Sequelize.col('question_id')), 'count'] ], + where: { + sem_id: settings.get_admin_settings().currSem, + help_time: { + [Sequelize.Op.ne]: null + }, + }, group: [Sequelize.fn('date_part', 'dow', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`))], order: [[Sequelize.col('count'), 'DESC']] }).then((data) => { @@ -356,6 +375,12 @@ exports.get_num_students_overall = (req, res) => { [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`)), 'day'], [Sequelize.fn('count', Sequelize.col('question_id')), 'count'] ], + where: { + sem_id: settings.get_admin_settings().currSem, + help_time: { + [Sequelize.Op.ne]: null + }, + }, group: [Sequelize.fn('date', Sequelize.literal(`"entry_time" AT TIME ZONE 'EST'`))], order: [[Sequelize.col('day'), 'ASC']] }).then((data) => { @@ -386,8 +411,10 @@ exports.get_ranked_students = (req, res) => { } }).then((questionModels) => { + for (const questionModel of questionModels) { let question = questionModel.dataValues; + console.log(question); if (question.student_id in studentMap) { studentMap[question.student_id].count++; From 773445329d3483db9352464470d3803edd6ee897 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Fri, 12 Jan 2024 23:58:12 -0500 Subject: [PATCH 20/26] Enforce semester on today's stats --- server/controllers/metrics.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index 2f661bb..f6f13a8 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -125,6 +125,7 @@ exports.get_num_questions_today = (req, res) => { entry_time: { [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count}) => { respond(req, res, "Got number of questions today", { numQuestionsToday: count }, 200); @@ -145,7 +146,8 @@ exports.get_num_bad_questions_today = (req, res) => { }, num_asked_to_fix: { [Sequelize.Op.gt]: 0 - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count}) => { respond(req, res, "Got number of bad questions today", { numBadQuestionsToday: count }, 200); @@ -165,7 +167,8 @@ exports.get_avg_wait_time_today = (req, res) => { }, help_time: { [Sequelize.Op.ne]: null, - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count, rows}) => { @@ -192,7 +195,8 @@ exports.get_ta_student_ratio_today = (req, res) => { where: { entry_time: { [Sequelize.Op.gte]: new Date(today - 8 * 60 * 60 * 1000), - } + }, + sem_id: settings.get_admin_settings().currSem, } }).then(({count, rows}) => { const taCount = rows.reduce((acc, questionModel) => { From c300254a8f84d5f61a02d061dc7e448cf142483f Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Sat, 13 Jan 2024 00:00:42 -0500 Subject: [PATCH 21/26] Formatting and naming on metrics page --- client/src/components/metrics/AdminMetrics.tsx | 2 +- client/src/components/metrics/Graph.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/metrics/AdminMetrics.tsx b/client/src/components/metrics/AdminMetrics.tsx index 9e6cf4b..c074f5f 100644 --- a/client/src/components/metrics/AdminMetrics.tsx +++ b/client/src/components/metrics/AdminMetrics.tsx @@ -77,7 +77,7 @@ export default function AdminMetrics() { } spacing={2} sx={{m: 2}} diff --git a/client/src/components/metrics/Graph.tsx b/client/src/components/metrics/Graph.tsx index f8a3454..383b749 100644 --- a/client/src/components/metrics/Graph.tsx +++ b/client/src/components/metrics/Graph.tsx @@ -45,7 +45,7 @@ export default function Graph() { return (
- Number of Students per Day (in the last week) + Number of Questions per Day (in the last week)
@@ -114,7 +114,7 @@ export default function Graph() {
- Number of Students per Day (overall) + Number of Questions per Day (semester)
@@ -183,7 +183,7 @@ export default function Graph() {
- Number of Students per Day of the Week + Number of Questions per Day of the Week
From c29ae2da335583aaec8a8fdfea10ea6e25332a5b Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Sat, 13 Jan 2024 00:08:20 -0500 Subject: [PATCH 22/26] Add number of times asked to fix to student rank --- client/src/components/metrics/AdminMetrics.tsx | 5 +++-- server/controllers/metrics.js | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/components/metrics/AdminMetrics.tsx b/client/src/components/metrics/AdminMetrics.tsx index c074f5f..90d8a9a 100644 --- a/client/src/components/metrics/AdminMetrics.tsx +++ b/client/src/components/metrics/AdminMetrics.tsx @@ -54,7 +54,8 @@ export default function AdminMetrics() { const studentCols = [ {id: 'student_andrew', label: 'Andrew ID', width: 25}, {id: 'student_name', label: 'Name', width: 25}, - {id: 'count', label: 'Total Questions', width: 100}, + {id: 'count', label: 'Num Questions', width: 100}, + {id: 'badCount', label: 'Num Ask to Fix', width: 100}, {id: 'timeHelped', label: 'Total Helping Time (min)', width: 100}, {id: 'average', label: 'Average Helping Time (min)', width: 100}, ]; @@ -62,7 +63,7 @@ export default function AdminMetrics() { const taCols = [ {id: 'ta_andrew', label: 'Andrew ID', width: 25}, {id: 'ta_name', label: 'Name', width: 25}, - {id: 'count', label: 'Total Questions Answered', width: 100}, + {id: 'count', label: 'Num Questions Answered', width: 100}, {id: 'timeHelping', label: 'Total Time Helping (min)', width: 100}, {id: 'average', label: 'Average Time Helping (min)', width: 100}, ]; diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index f6f13a8..19f64b9 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -423,10 +423,12 @@ exports.get_ranked_students = (req, res) => { if (question.student_id in studentMap) { studentMap[question.student_id].count++; studentMap[question.student_id].timeHelped += (question.exit_time - question.help_time) / 1000 / 60; + studentMap[question.student_id].badCount += parseInt(question.num_asked_to_fix); } else { studentMap[question.student_id] = { count: 1, timeHelped: (question.exit_time - question.help_time) / 1000 / 60, + badCount: parseInt(question.num_asked_to_fix) }; } } @@ -449,6 +451,7 @@ exports.get_ranked_students = (req, res) => { student_name: accountData.preferred_name, student_andrew: accountData.email.split("@")[0], count: studentMap[user_id].count, + badCount: studentMap[user_id].badCount, timeHelped: Math.round(studentMap[user_id].timeHelped * 10) / 10, }); } From 335002aa21a5d70371feaf86f5f6a030f5049061 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Sat, 13 Jan 2024 00:08:56 -0500 Subject: [PATCH 23/26] Remove console.log in metrics.js --- server/controllers/metrics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/controllers/metrics.js b/server/controllers/metrics.js index 19f64b9..4b242ae 100644 --- a/server/controllers/metrics.js +++ b/server/controllers/metrics.js @@ -418,7 +418,6 @@ exports.get_ranked_students = (req, res) => { for (const questionModel of questionModels) { let question = questionModel.dataValues; - console.log(question); if (question.student_id in studentMap) { studentMap[question.student_id].count++; From c8919f2c80dece9ccf7ba39abd8a424bf92ae615 Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Sat, 13 Jan 2024 00:12:51 -0500 Subject: [PATCH 24/26] clean up unused imports and other compilation warnings --- client/src/components/home/shared/AskQuestion.tsx | 3 --- client/src/components/home/shared/QueueStats.tsx | 2 +- client/src/components/home/student/UpdateQuestionOverlay.tsx | 2 +- client/src/components/home/ta/dialogs/FilterOptions.tsx | 3 +-- client/src/components/metrics/MetricsMain.tsx | 2 -- client/src/components/settings/admin/DayPicker.tsx | 2 +- client/src/components/settings/admin/Locations.tsx | 2 +- client/src/components/settings/admin/TASettings.tsx | 2 +- 8 files changed, 6 insertions(+), 12 deletions(-) diff --git a/client/src/components/home/shared/AskQuestion.tsx b/client/src/components/home/shared/AskQuestion.tsx index b63e5ad..f893c33 100644 --- a/client/src/components/home/shared/AskQuestion.tsx +++ b/client/src/components/home/shared/AskQuestion.tsx @@ -10,7 +10,6 @@ import BaseCard from '../../common/cards/BaseCard'; import HomeService from '../../../services/HomeService'; import {UserDataContext} from '../../../contexts/UserDataContext'; import {QueueDataContext} from '../../../contexts/QueueDataContext'; -import {StudentDataContext} from '../../../contexts/StudentDataContext'; function createData(assignment_id, name) { return {assignment_id, name}; @@ -34,8 +33,6 @@ export default function AskQuestion() { const [askDisabled, setAskDisabled] = useState(false); - const {studentData, setStudentData} = useContext(StudentDataContext); - const locations = useMemo(() => { if (queueData != null) { const day = date.getDay(); diff --git a/client/src/components/home/shared/QueueStats.tsx b/client/src/components/home/shared/QueueStats.tsx index 3e636f0..74af2f5 100644 --- a/client/src/components/home/shared/QueueStats.tsx +++ b/client/src/components/home/shared/QueueStats.tsx @@ -1,4 +1,4 @@ -import React, {useContext, useEffect} from 'react'; +import React, {useContext} from 'react'; import { CardContent, Divider, Stack, Typography, useTheme, } from '@mui/material'; diff --git a/client/src/components/home/student/UpdateQuestionOverlay.tsx b/client/src/components/home/student/UpdateQuestionOverlay.tsx index 4939ba5..eb1181b 100644 --- a/client/src/components/home/student/UpdateQuestionOverlay.tsx +++ b/client/src/components/home/student/UpdateQuestionOverlay.tsx @@ -1,6 +1,6 @@ import React, {useContext, useState} from 'react'; import { - Button, Dialog, DialogContent, FormControl, Input, Link, Stack, Typography, + Button, Dialog, DialogContent, FormControl, Input, Link, Typography, } from '@mui/material'; import HomeService from '../../../services/HomeService'; diff --git a/client/src/components/home/ta/dialogs/FilterOptions.tsx b/client/src/components/home/ta/dialogs/FilterOptions.tsx index 0fc3200..4f234d3 100644 --- a/client/src/components/home/ta/dialogs/FilterOptions.tsx +++ b/client/src/components/home/ta/dialogs/FilterOptions.tsx @@ -1,10 +1,9 @@ -import React, {useState, useEffect, useContext, useMemo} from 'react'; +import React, {useContext, useMemo} from 'react'; import {List, ListSubheader, ListItem, ListItemButton, ListItemIcon, ListItemText, Checkbox, } from '@mui/material'; -import SettingsService from '../../../../services/SettingsService'; import {QueueDataContext} from '../../../../contexts/QueueDataContext'; function createData(assignment_id, name) { diff --git a/client/src/components/metrics/MetricsMain.tsx b/client/src/components/metrics/MetricsMain.tsx index bff88ea..247238d 100644 --- a/client/src/components/metrics/MetricsMain.tsx +++ b/client/src/components/metrics/MetricsMain.tsx @@ -3,7 +3,6 @@ import { Typography, } from '@mui/material'; -import DateTimeSelector from './DateTimeSelector'; import PersonalStats from './PersonalStats'; import OverallStats from './OverallStats'; import CumulativeStats from './CumulativeStats'; @@ -19,7 +18,6 @@ export default function MetricsMain(props) { Metrics - {/* */} diff --git a/client/src/components/settings/admin/DayPicker.tsx b/client/src/components/settings/admin/DayPicker.tsx index bbd6d34..fbc8352 100644 --- a/client/src/components/settings/admin/DayPicker.tsx +++ b/client/src/components/settings/admin/DayPicker.tsx @@ -8,7 +8,7 @@ import {Delete as DeleteIcon} from '@mui/icons-material'; import SettingsService from '../../../services/SettingsService'; export default function DayPicker(props) { - const {convertIdxToDays, daysOfWeek, room, roomDictionary, setRoomDictionary} = props; + const {convertIdxToDays, daysOfWeek, room, roomDictionary} = props; const [newDays, setNewDays] = useState(convertIdxToDays(roomDictionary[room])); const convertDaysToIdx = (daysArr) => { diff --git a/client/src/components/settings/admin/Locations.tsx b/client/src/components/settings/admin/Locations.tsx index ae8b009..a820d10 100644 --- a/client/src/components/settings/admin/Locations.tsx +++ b/client/src/components/settings/admin/Locations.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useContext, useMemo} from 'react'; +import React, {useState, useContext, useMemo} from 'react'; import { TableCell, Typography, } from '@mui/material'; diff --git a/client/src/components/settings/admin/TASettings.tsx b/client/src/components/settings/admin/TASettings.tsx index f7f52f8..21e1b11 100644 --- a/client/src/components/settings/admin/TASettings.tsx +++ b/client/src/components/settings/admin/TASettings.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useContext, useMemo} from 'react'; +import React, {useState, useContext, useMemo} from 'react'; import { Button, Checkbox, FormControlLabel, Grid, TableCell, TableRow, Typography, useTheme, } from '@mui/material'; From 8dd6d981333af0a2d14665f9115c468b76c16e2d Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Sat, 13 Jan 2024 01:52:20 -0500 Subject: [PATCH 25/26] Add option to enforce CMU email as an admin setting --- .../settings/admin/ConfigSettings.tsx | 36 +++++++++++-- client/src/contexts/AdminSettingsContext.tsx | 2 + client/src/services/SettingsService.tsx | 3 ++ server/controllers/login.js | 4 ++ server/controllers/settings.js | 54 ++++++++++++++++--- server/routes/settings.js | 1 + types/AdminSettings.ts | 1 + 7 files changed, 90 insertions(+), 11 deletions(-) diff --git a/client/src/components/settings/admin/ConfigSettings.tsx b/client/src/components/settings/admin/ConfigSettings.tsx index 094b98f..4456f23 100644 --- a/client/src/components/settings/admin/ConfigSettings.tsx +++ b/client/src/components/settings/admin/ConfigSettings.tsx @@ -1,7 +1,7 @@ import React, {useState, useEffect, useContext} from 'react'; import { - Button, CardContent, Typography, TextField, Grid, + Button, CardContent, Typography, TextField, Grid, Checkbox, } from '@mui/material'; import BaseCard from '../../common/cards/BaseCard'; @@ -17,12 +17,14 @@ export default function ConfigSettings(props) { const [currSem, setCurrSem] = useState(''); const [slackURL, setSlackURL] = useState(''); const [questionsURL, setQuestionsURL] = useState(''); + const [enforceCMUEmail, setEnforceCMUEmail] = useState(true); useEffect(() => { setCurrSem(adminSettings.currSem); setSlackURL(adminSettings.slackURL); + setEnforceCMUEmail(adminSettings.enforceCMUEmail); setQuestionsURL(queueData.questionsURL); - }, [adminSettings]); + }, [adminSettings, queueData]); const handleUpdateSemester = (event) => { event.preventDefault(); @@ -60,6 +62,16 @@ export default function ConfigSettings(props) { ); }; + const handleUpdateCmuEmailEnabled = (event) => { + event.preventDefault(); + + SettingsService.updateEnforceCmuEmail( + JSON.stringify({ + enforceCMUEmail: enforceCMUEmail, + }), + ); + }; + return ( @@ -87,8 +99,26 @@ export default function ConfigSettings(props) { -
+ + + Enforce CMU Email: + { + setEnforceCMUEmail(e.target.checked); + }} + /> + + + + + +
+
+ {}) as React.Dispatch>, }); @@ -27,6 +28,7 @@ const AdminSettingsContextProvider = ({children}: {children: React.ReactNode}) = const [adminSettings, setAdminSettings] = useState({ currSem: '', slackURL: undefined, + enforceCMUEmail: true, }); // Load admin settings if user is an admin diff --git a/client/src/services/SettingsService.tsx b/client/src/services/SettingsService.tsx index d6bcf3c..1b5c39a 100644 --- a/client/src/services/SettingsService.tsx +++ b/client/src/services/SettingsService.tsx @@ -60,6 +60,9 @@ class SettingsDataService { updateRejoinTime(data) { return http.post('/settings/config/rejoin/update', data); } + updateEnforceCmuEmail(data) { + return http.post('/settings/config/enforcecmuemail/update', data); + } updatePreferredName(data) { return http.post('/settings/preferredname/update', data); } diff --git a/server/controllers/login.js b/server/controllers/login.js index 024f990..c8c6495 100644 --- a/server/controllers/login.js +++ b/server/controllers/login.js @@ -57,6 +57,10 @@ exports.post_login = async (req, res) => { res.status(500); res.json({ message: "Queue has not been initialized; must log in with owner email" }); return; + } else if (adminSettings.enforceCMUEmail && !(email.endsWith("@andrew.cmu.edu") || email.endsWith("@cmu.edu"))) { + res.status(500); + res.json({ message: "Must log in with a CMU email" }); + return; } Promise.props({ diff --git a/server/controllers/settings.js b/server/controllers/settings.js index 8708fe2..fd3561d 100644 --- a/server/controllers/settings.js +++ b/server/controllers/settings.js @@ -12,13 +12,23 @@ const home = require('./home'); // FIXME: some default values are set to simplify testing; // In production, these should be cleared var fs = require('fs'); -let adminSettings = { +const defaultAdminSettings = { currSem: "S23", slackURL: null, questionsURL: '', rejoinTime: 15, + enforceCMUEmail: true, dayDictionary: {} }; + +let adminSettings = defaultAdminSettings + +function haveSameKeys(obj1, obj2) { + const obj1Keys = Object.keys(obj1).sort(); + const obj2Keys = Object.keys(obj2).sort(); + return JSON.stringify(obj1Keys) === JSON.stringify(obj2Keys); +} + // If no admin setting have been generated, use the above default values if (!fs.existsSync('../adminSettings.json')) { var json = JSON.stringify(adminSettings) @@ -26,6 +36,20 @@ if (!fs.existsSync('../adminSettings.json')) { console.log('Created admin settings JSON'); }); } +// If admin settings have been generated, but the keys don't match, update the missing keys +else if (fs.existsSync("../adminSettings.json") && !haveSameKeys(adminSettings, JSON.parse(fs.readFileSync('../adminSettings.json', 'utf8')))) { + let currAdminSettings = fs.readFileSync('../adminSettings.json', 'utf8', flag = 'r+'); + let newAdminSettings = JSON.parse(currAdminSettings); + for (let key in adminSettings) { + if (!newAdminSettings.hasOwnProperty(key)) { + newAdminSettings[key] = adminSettings[key]; + } + } + var json = JSON.stringify(newAdminSettings) + fs.writeFileSync('../adminSettings.json', json, 'utf8', function () { + console.log('Updated admin settings JSON'); + }); +} exports.get_admin_settings = function () { @@ -34,13 +58,7 @@ exports.get_admin_settings = function () { adminSettings = JSON.parse(data); } else { console.log('No admin settings found'); - adminSettings = { - currSem: "S23", - slackURL: null, - questionsURL: '', - rejoinTime: 15, - dayDictionary: {} - }; + adminSettings = defaultAdminSettings } return adminSettings; @@ -277,6 +295,26 @@ exports.post_update_rejoin_time = function (req, res) { respond_success(req, res, `Rejoin time updated successfully to ${rejoinTime} minutes`); } +exports.post_update_enforce_cmu_email = function (req, res) { + if (!req.user || !req.user.isAdmin) { + respond_error(req, res, "You don't have permissions to perform this operation", 403); + return; + } + + var enforceCMUEmail = req.body.enforceCMUEmail; + + if (enforceCMUEmail == null || enforceCMUEmail == undefined) { + respond_error(req, res, "Invalid/missing parameters in request", 400); + return; + } + + if (adminSettings.enforceCMUEmail == enforceCMUEmail) return; + + adminSettings.enforceCMUEmail = enforceCMUEmail; + writeAdminSettings(adminSettings); + respond_success(req, res, `Enforcing CMU email updated successfully to: ${enforceCMUEmail}`); +} + /** Topics Functions **/ exports.post_create_topic = function (req, res) { if (!req.user || !req.user.isAdmin) { diff --git a/server/routes/settings.js b/server/routes/settings.js index a41c9f4..7b3c12f 100644 --- a/server/routes/settings.js +++ b/server/routes/settings.js @@ -25,6 +25,7 @@ router.post('/config/sem/update', settings.post_update_semester); router.post('/config/slack/update', settings.post_update_slack_url); router.post('/config/questions/update', settings.post_update_questions_url); router.post('/config/rejoin/update', settings.post_update_rejoin_time); +router.post('/config/enforcecmuemail/update', settings.post_update_enforce_cmu_email); router.post('/locations/update', settings.post_update_locations); router.post('/locations/add', settings.add_location); diff --git a/types/AdminSettings.ts b/types/AdminSettings.ts index ce94a0f..cb35a88 100644 --- a/types/AdminSettings.ts +++ b/types/AdminSettings.ts @@ -4,4 +4,5 @@ export type AdminSettings = { currSem: string, slackURL: string | undefined, + enforceCMUEmail: boolean, } From 3f03d73019c9c2882cfc7a4742484f35c3278e7e Mon Sep 17 00:00:00 2001 From: Jackson Romero Date: Sat, 13 Jan 2024 02:22:05 -0500 Subject: [PATCH 26/26] Fix pagination with unique keys --- client/src/components/metrics/AdminMetrics.tsx | 4 ++-- client/src/components/metrics/PersonalStats.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/metrics/AdminMetrics.tsx b/client/src/components/metrics/AdminMetrics.tsx index 90d8a9a..9e9ea73 100644 --- a/client/src/components/metrics/AdminMetrics.tsx +++ b/client/src/components/metrics/AdminMetrics.tsx @@ -103,7 +103,7 @@ export default function AdminMetrics() { .slice(studentPage * rowsPerStudentPage, studentPage * rowsPerStudentPage + rowsPerStudentPage) .map((row) => { return ( - + {studentCols.map((column) => { const value = row[column.id]; return ( @@ -149,7 +149,7 @@ export default function AdminMetrics() { .slice(taPage * rowsPerTAPage, taPage * rowsPerTAPage + rowsPerTAPage) .map((row) => { return ( - + {taCols.map((column) => { const value = row[column.id]; return ( diff --git a/client/src/components/metrics/PersonalStats.tsx b/client/src/components/metrics/PersonalStats.tsx index 6cdc76c..746d29f 100644 --- a/client/src/components/metrics/PersonalStats.tsx +++ b/client/src/components/metrics/PersonalStats.tsx @@ -107,9 +107,9 @@ export default function PersonalStats() { {helpedStudents .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => { + .map((row, i) => { return ( - + {columns.map((column) => { const value = row[column.id]; return (