diff --git a/client/src/components/home/shared/CooldownViolationOverlay.tsx b/client/src/components/home/shared/CooldownViolationOverlay.tsx index fcc37c5..0eae8a3 100644 --- a/client/src/components/home/shared/CooldownViolationOverlay.tsx +++ b/client/src/components/home/shared/CooldownViolationOverlay.tsx @@ -12,41 +12,59 @@ export default function CooldownViolationOverlay(props) { const {queueData} = useContext(QueueDataContext); function callAddQuestionAPIOverrideCooldown() { - HomeService.addQuestion( - JSON.stringify({ - andrewID: andrewID, - question: question, - location: location, - topic: topic, - overrideCooldown: true, - }), - ).then((res) => { - if (res.status === 200) { - setOpen(false); - } - }); + if (queueData.allowCDOverride) { + HomeService.addQuestion( + JSON.stringify({ + andrewID: andrewID, + question: question, + location: location, + topic: topic, + overrideCooldown: true, + }), + ).then((res) => { + if (res.status === 200) { + setOpen(false); + } + }); + } else return; } - return ( - - - - You rejoined the queue too quickly! Please wait for {queueData.rejoinTime} minutes after finishing your last question, which will be in {queueData.rejoinTime - timePassed} minutes. - + if (queueData.allowCDOverride) { + return ( + + + + You rejoined the queue too quickly! Please wait for {queueData.rejoinTime} minutes after finishing your last question, which will be in {queueData.rejoinTime - timePassed} minutes. + - - + + + + + + + Overriding the cooldown will add you to the queue, however you will be frozen until a TA approves you. + + + + ); + } else { + return ( + + + + You rejoined the queue too quickly! Please wait for {queueData.rejoinTime} minutes after finishing your last question, which will be in {queueData.rejoinTime - timePassed} minutes. + - - - - Overriding the cooldown will add you to the queue, however you will be frozen until a TA approves you. - - - - ); + + + ); + } } + diff --git a/client/src/components/home/student/YourEntry.tsx b/client/src/components/home/student/YourEntry.tsx index 0a54f86..32b5639 100644 --- a/client/src/components/home/student/YourEntry.tsx +++ b/client/src/components/home/student/YourEntry.tsx @@ -23,6 +23,8 @@ export default function YourEntry(props) { const {queueData} = useContext(QueueDataContext); const {studentData} = useContext(StudentDataContext); + const cooldownMsg = queueData.allowCDOverride ? 'You have been frozen in line. This means you will not advance in the queue until a TA approves your entry.' : 'You have been frozen in line. You will not advance in the queue! Please wait for your cooldown to end before joining the queue again.'; + return ( @@ -41,8 +43,7 @@ export default function YourEntry(props) { - You have been frozen in line. This means you will not advance in the queue - until a TA approves your entry. + {cooldownMsg} diff --git a/client/src/components/home/ta/EntryTails.tsx b/client/src/components/home/ta/EntryTails.tsx index d201e89..5350245 100644 --- a/client/src/components/home/ta/EntryTails.tsx +++ b/client/src/components/home/ta/EntryTails.tsx @@ -10,10 +10,13 @@ import StudentStatus from './TailOptions/StudentStatus'; import LeapStudentActions from './TailOptions/LeapStudentActions'; import {StudentStatusValues} from '../../../services/StudentStatus'; +import PersistentOptions from './TailOptions/PersistentOptions'; export default function EntryTails(props) { const {student, index, isHelping, helpIdx} = props; + const showApproval = props.showCooldownApproval; + const status = student.status; const themeHook = useTheme(); @@ -30,8 +33,13 @@ export default function EntryTails(props) { case StudentStatusValues.WAITING: return (ActionsHelp(props)); case StudentStatusValues.FIXING_QUESTION: return (ActionsHelp(props)); case StudentStatusValues.FROZEN: return (ActionsFreeze(props)); - case StudentStatusValues.COOLDOWN_VIOLATION: return (LeapStudentActions(props)); case StudentStatusValues.RECEIVED_MESSAGE: return (ActionsHelp(props)); + case StudentStatusValues.COOLDOWN_VIOLATION: + if (showApproval) { + return (LeapStudentActions(props)); + } else { + return (ActionsHelp({...props, color: 'secondary'})); + } default: return; } }; diff --git a/client/src/components/home/ta/StudentEntry.tsx b/client/src/components/home/ta/StudentEntry.tsx index a15cf2b..b038a6b 100644 --- a/client/src/components/home/ta/StudentEntry.tsx +++ b/client/src/components/home/ta/StudentEntry.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useRef} from 'react'; +import React, {useState, useEffect, useRef, useContext} from 'react'; import { Stack, TableCell, Typography, } from '@mui/material'; @@ -7,16 +7,21 @@ import PauseIcon from '@mui/icons-material/Pause'; import EntryTails from './EntryTails'; import ItemRow from '../../common/table/ItemRow'; +import {QueueDataContext} from '../../../contexts/QueueDataContext'; + import HomeService from '../../../services/HomeService'; import {StudentStatusValues} from '../../../services/StudentStatus'; +import {Settings} from '@mui/icons-material'; +import {debug} from 'util'; export default function StudentEntry(props) { + const {queueData} = useContext(QueueDataContext); const {student, index, handleClickHelp, removeStudent, handleClickUnfreeze} = props; const [confirmRemove, setConfirmRemove] = useState(false); const removeRef = useRef(); - const [showCooldownApproval, setShowCooldownApproval] = useState(student['status'] === StudentStatusValues.COOLDOWN_VIOLATION); + const [showCooldownApproval, setShowCooldownApproval] = useState(queueData.allowCDOverride && student['status'] === StudentStatusValues.COOLDOWN_VIOLATION); useEffect(() => { const closeExpanded = (e) => { @@ -32,6 +37,11 @@ export default function StudentEntry(props) { }; }, []); + // Update showCooldownApproval when allowCDOverride changes + useEffect(() => { + setShowCooldownApproval(queueData.allowCDOverride && student['status'] === StudentStatusValues.COOLDOWN_VIOLATION); + }, [queueData.allowCDOverride, student['status']]); + function handleRemoveButton() { if (confirmRemove) { setConfirmRemove(false); diff --git a/client/src/components/home/ta/TailOptions/ActionsHelp.tsx b/client/src/components/home/ta/TailOptions/ActionsHelp.tsx index bef9a1c..fdc65e0 100644 --- a/client/src/components/home/ta/TailOptions/ActionsHelp.tsx +++ b/client/src/components/home/ta/TailOptions/ActionsHelp.tsx @@ -12,12 +12,14 @@ export default function ActionsHelp(props) { student, index, isHelping, handleClickHelp, } = props; + const buttonColor = props.color == null ? 'info' : props.color; + return ( - {PersistentOptions(props)} diff --git a/client/src/components/metrics/Graph.tsx b/client/src/components/metrics/Graph.tsx index 383b749..723380d 100644 --- a/client/src/components/metrics/Graph.tsx +++ b/client/src/components/metrics/Graph.tsx @@ -103,8 +103,8 @@ export default function Graph() { label: 'Number of Students', data: numStudentsPerDayLastWeek.map((day) => day.students), fill: false, - backgroundColor: theme.palette.primary.main, - borderColor: theme.palette.primary.main, + backgroundColor: theme.palette.secondary.main, + borderColor: theme.palette.secondary.main, borderWidth: 3, tension: 0.3, }, @@ -172,8 +172,8 @@ export default function Graph() { label: 'Number of Students', data: numStudentsOverall.map((day) => day.students), fill: false, - backgroundColor: theme.palette.primary.main, - borderColor: theme.palette.primary.main, + backgroundColor: theme.palette.secondary.main, + borderColor: theme.palette.secondary.main, borderWidth: 3, tension: 0.3, }, @@ -239,8 +239,8 @@ export default function Graph() { { label: 'Number of Students', data: numStudentsPerDay.map((day) => day.students), - backgroundColor: theme.palette.primary.main, - borderColor: theme.palette.primary.main, + backgroundColor: theme.palette.secondary.main, + borderColor: theme.palette.secondary.main, borderWidth: 3, }, ], diff --git a/client/src/components/settings/admin/ConfigSettings.tsx b/client/src/components/settings/admin/ConfigSettings.tsx index 4456f23..2ead9b4 100644 --- a/client/src/components/settings/admin/ConfigSettings.tsx +++ b/client/src/components/settings/admin/ConfigSettings.tsx @@ -18,13 +18,17 @@ export default function ConfigSettings(props) { const [slackURL, setSlackURL] = useState(''); const [questionsURL, setQuestionsURL] = useState(''); const [enforceCMUEmail, setEnforceCMUEmail] = useState(true); + const [allowCDOverride, setAllowCDOverride] = useState(true); useEffect(() => { setCurrSem(adminSettings.currSem); setSlackURL(adminSettings.slackURL); setEnforceCMUEmail(adminSettings.enforceCMUEmail); + }, [adminSettings]); + useEffect(() => { + setAllowCDOverride(queueData.allowCDOverride); setQuestionsURL(queueData.questionsURL); - }, [adminSettings, queueData]); + }, [queueData]); const handleUpdateSemester = (event) => { event.preventDefault(); @@ -72,6 +76,16 @@ export default function ConfigSettings(props) { ); }; + const handleCooldownOverrideEnabled = (event) => { + event.preventDefault(); + + SettingsService.updateAllowCDOverride( + JSON.stringify({ + allowCDOverride: allowCDOverride, + }), + ); + }; + return ( @@ -117,6 +131,24 @@ export default function ConfigSettings(props) { +
+ + + Allow Cooldown Override: + { + setAllowCDOverride(e.target.checked); + }} + /> + + + + + +
diff --git a/client/src/contexts/QueueDataContext.tsx b/client/src/contexts/QueueDataContext.tsx index c34fea4..0f78287 100644 --- a/client/src/contexts/QueueDataContext.tsx +++ b/client/src/contexts/QueueDataContext.tsx @@ -24,6 +24,7 @@ const QueueDataContextProvider = ({children}: {children: React.ReactNode}) => { title: '15-122 Office Hours Queue', uninitializedSem: false, queueFrozen: true, + allowCDOverride: true, numStudents: 0, rejoinTime: 15, diff --git a/client/src/services/SettingsService.tsx b/client/src/services/SettingsService.tsx index 1b5c39a..6528910 100644 --- a/client/src/services/SettingsService.tsx +++ b/client/src/services/SettingsService.tsx @@ -63,6 +63,9 @@ class SettingsDataService { updateEnforceCmuEmail(data) { return http.post('/settings/config/enforcecmuemail/update', data); } + updateAllowCDOverride(data) { + return http.post('/settings/config/allowcdoverride/update', data); + } updatePreferredName(data) { return http.post('/settings/preferredname/update', data); } diff --git a/client/src/themes/base.tsx b/client/src/themes/base.tsx index 13a4911..cd8a39d 100644 --- a/client/src/themes/base.tsx +++ b/client/src/themes/base.tsx @@ -26,10 +26,10 @@ const lightTheme = createTheme({ mode: 'light', primary: { // main: '#EF8EC3', - main: '#015122', + main: '#014122', }, secondary: { - main: '#EA3947', + main: '#f4cd2a', }, success: { main: '#43a047', @@ -49,7 +49,7 @@ const lightTheme = createTheme({ cancel: '#9e9e9e', unfreeze: '#ba68c8', // navbar: '#EF8EC3', - navbar: '#015122', + navbar: '#014122', }, components: { MuiButton: { @@ -121,10 +121,10 @@ const darkTheme = createTheme({ mode: 'dark', primary: { // main: '#EF8EC3', - main: '#015122', + main: '#014122', }, secondary: { - main: '#e8152e', + main: '#f4cd2a', }, success: { main: '#09e312', @@ -142,12 +142,12 @@ const darkTheme = createTheme({ }, alternateColors: { // darkerPrimary: '#e36bac', - darkerPrimary: '#013122', + darkerPrimary: '#014122', alternatePaper: '#575757', cancel: '#9e9e9e', unfreeze: '#ba68c8', // navbar: '#e36bac', - navbar: '#013122', + navbar: '#014122', }, components: { MuiButton: { diff --git a/server/controllers/home.js b/server/controllers/home.js index 10e412d..867136d 100644 --- a/server/controllers/home.js +++ b/server/controllers/home.js @@ -77,6 +77,7 @@ function buildQueueData() { title: "15-122 Office Hours Queue", uninitializedSem: adminSettings.currSem == null, queueFrozen: queueFrozen, + allowCDOverride: adminSettings.allowCDOverride, // global stats numStudents: ohq.size(), @@ -515,8 +516,12 @@ exports.post_add_question = function (req, res) { if (!student) { throw new Error('No existing student account with provided andrew ID.'); } - + + let allowCDOverride = settings.get_admin_settings().allowCDOverride; // check for cooldown violation + if (overrideCooldown && !allowCDOverride) { + throw new Error('Cooldown override is disabled'); + } let rejoinTime = settings.get_admin_settings().rejoinTime return Promise.props({ questions: models.question.findAll({ @@ -888,6 +893,12 @@ exports.post_approve_cooldown_override = function (req, res) { respond_error(req, res, "This request was not made by a TA", 400); return } + + let cooldownAllowed = settings.get_admin_settings().allowCDOverride + if (!cooldownAllowed) { + respond_error(req, res, "Cooldown Override has been disabled", 400) + return + } let id = req.body.andrewID if (ohq.getPosition(id) === -1) { diff --git a/server/controllers/settings.js b/server/controllers/settings.js index fd3561d..3a407ac 100644 --- a/server/controllers/settings.js +++ b/server/controllers/settings.js @@ -18,6 +18,7 @@ const defaultAdminSettings = { questionsURL: '', rejoinTime: 15, enforceCMUEmail: true, + allowCDOverride: true, dayDictionary: {} }; @@ -52,7 +53,6 @@ else if (fs.existsSync("../adminSettings.json") && !haveSameKeys(adminSettings, } exports.get_admin_settings = function () { - let data = fs.readFileSync('../adminSettings.json', 'utf8', flag = 'r+'); if (data) { adminSettings = JSON.parse(data); @@ -315,6 +315,27 @@ exports.post_update_enforce_cmu_email = function (req, res) { respond_success(req, res, `Enforcing CMU email updated successfully to: ${enforceCMUEmail}`); } +exports.post_update_allow_cooldown_override = function (req, res) { + if (!req.user || !req.user.isAdmin) { + respond_error(req, res, "You don't have permission to perform this operation", 403); + return; + } + + var allowCDOverride = req.body.allowCDOverride; + + if (allowCDOverride == null || allowCDOverride == undefined) { + respond_error(req, res, "Invalid/missing parameters in request", 400); + return; + } + + if (adminSettings.allowCDOverride == allowCDOverride) return; + + adminSettings.allowCDOverride = allowCDOverride; + writeAdminSettings(adminSettings); + home.emit_new_queue_data(); + respond_success(req, res, `Allow Cooldown Override updated successfuly to: ${allowCDOverride}`) +} + /** Topics Functions **/ exports.post_create_topic = function (req, res) { if (!req.user || !req.user.isAdmin) { diff --git a/server/controllers/sockets.js b/server/controllers/sockets.js index ff92955..aff98a1 100644 --- a/server/controllers/sockets.js +++ b/server/controllers/sockets.js @@ -63,7 +63,7 @@ exports.queueData = function (queueData) { console.log("ERROR: Socket.io is not initialized yet"); return; } - + sio.emit("queueData", { ...queueData }); diff --git a/server/routes/settings.js b/server/routes/settings.js index 7b3c12f..26d36bb 100644 --- a/server/routes/settings.js +++ b/server/routes/settings.js @@ -26,6 +26,7 @@ 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('/config/allowcdoverride/update', settings.post_update_allow_cooldown_override); router.post('/locations/update', settings.post_update_locations); router.post('/locations/add', settings.add_location); diff --git a/types/QueueData.ts b/types/QueueData.ts index 3d5481b..527fca0 100644 --- a/types/QueueData.ts +++ b/types/QueueData.ts @@ -6,6 +6,7 @@ export type QueueData = { title: string, uninitializedSem: boolean, queueFrozen: boolean, + allowCDOverride: boolean, // global stats numStudents: number