From 9189767a395b914480765f1bab0078ff7eef0427 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Sun, 20 Oct 2024 12:29:11 +0100 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=92=84=20Add=20button=20to=20end=20?= =?UTF-8?q?judging=20to=20admin=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once judging is over, button changes to display how many judges have yet to submit their rankings. --- .../components/admin/AdminToggleSwitch.tsx | 2 +- client/src/pages/admin/index.tsx | 54 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/client/src/components/admin/AdminToggleSwitch.tsx b/client/src/components/admin/AdminToggleSwitch.tsx index 7a4aa00..2f8f4d6 100644 --- a/client/src/components/admin/AdminToggleSwitch.tsx +++ b/client/src/components/admin/AdminToggleSwitch.tsx @@ -5,7 +5,7 @@ const AdminToggleSwitch = (props: { setState: React.Dispatch>; }) => { return ( -
+
{ const navigate = useNavigate(); const [showProjects, setShowProjects] = useState(true); const [loading, setLoading] = useState(true); + const [judgingEnded, setJudgingEnded] = useState(false); + const [numJudges, setNumJudges] = useState(0); useEffect(() => { // Check if user logged in @@ -33,10 +35,49 @@ const Admin = () => { errorAlert(loggedInRes); } - checkLoggedIn(); + + async function getNumJudges() { + const judgeListRes = await getRequest('/judge/list') + if (judgeListRes.status !== 200) { + errorAlert(judgeListRes); + return; + } + setNumJudges(judgeListRes.data?.length as number); + + } + getNumJudges(); + + async function checkJudgingEnded() { + // const judgingEndedRes = await getRequest('/admin/end_judging') + setJudgingEnded(false) + } + checkJudgingEnded(); }, []); + function endJudging() { + let confirmed = window.confirm("Are you sure you want to end judging? This cannot be undone.\n" + + "Judges will not be able to request new projects to rank and must submit rankings.") + if (confirmed) { + endJudgingReq().then(success => { + if (success) { + alert("Judging has now been ended. Judges will be notified and made to submit their rankings. " + + "Wait until all have submitted before recording final results.") + setJudgingEnded(true) + } else { + alert("Failed to end judging.") + } + }) + } + } + + async function endJudgingReq() { + console.log("Requesting server to end judging.") + // const endJudgingRes = await postRequest('/admin/end_judging', null) + // lucatodo: pause and remove clock + return true + } + if (loading) { return ; } @@ -51,6 +92,15 @@ const Admin = () => { className="absolute top-6 left-[16rem] w-40 md:w-52 text-lg py-2 px-1 hover:scale-100 focus:scale-100 rounded-md font-bold" >Settings +
+ +
From 69b09f5eb0b4568a02aa5deb8e2a9738a063c246 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Sun, 20 Oct 2024 13:04:56 +0100 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=92=84=20Update=20button=20UI=20to?= =?UTF-8?q?=20track=20judge=20submission=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/admin/index.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/client/src/pages/admin/index.tsx b/client/src/pages/admin/index.tsx index 90f4e7a..e215097 100644 --- a/client/src/pages/admin/index.tsx +++ b/client/src/pages/admin/index.tsx @@ -18,6 +18,7 @@ const Admin = () => { const [loading, setLoading] = useState(true); const [judgingEnded, setJudgingEnded] = useState(false); const [numJudges, setNumJudges] = useState(0); + const [submittedJudges, setSubmittedJudges] = useState(0); useEffect(() => { // Check if user logged in @@ -64,6 +65,7 @@ const Admin = () => { alert("Judging has now been ended. Judges will be notified and made to submit their rankings. " + "Wait until all have submitted before recording final results.") setJudgingEnded(true) + checkSubmittedJudges(); } else { alert("Failed to end judging.") } @@ -78,6 +80,22 @@ const Admin = () => { return true } + async function checkSubmittedJudges() { + console.log("Refreshing submitted judge count by counting current_projects array lengths") + const justListRes = await getRequest('/judge/list') + if (justListRes.status !== 200) { + errorAlert(justListRes); + return; + } + if (justListRes.data){ + let numSubmitted = 0 + justListRes.data.forEach(j => { + if (j.current_rankings.length == 0) numSubmitted++ + }) + setSubmittedJudges(numSubmitted) + } + } + if (loading) { return ; } @@ -92,14 +110,16 @@ const Admin = () => { className="absolute top-6 left-[16rem] w-40 md:w-52 text-lg py-2 px-1 hover:scale-100 focus:scale-100 rounded-md font-bold" >Settings -
+
+
+ className="justify-self-stretch md:w-full w-full" + >{judgingEnded ? `Submitted judges: ${submittedJudges}/${numJudges}` : "End Judging"} +
From e2188867c49b4e628349548e74148590d65ca363 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Sun, 20 Oct 2024 13:09:36 +0100 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Use=20consistent=20-?= =?UTF-8?q?s=20in=20API=20routes=20(and=20not=20=5F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/judge/index.tsx | 2 +- server/router/init.go | 2 +- server/router/judge.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/pages/judge/index.tsx b/client/src/pages/judge/index.tsx index 9add15a..dfce896 100644 --- a/client/src/pages/judge/index.tsx +++ b/client/src/pages/judge/index.tsx @@ -271,7 +271,7 @@ const Judge = () => { alert(`You can only submit rankings in batches of ${rankingBatchSize} projects.`) return } - const submitRes = await postRequest('/judge/submit_batch_ranking', { + const submitRes = await postRequest('/judge/submit-batch-ranking', { batch_ranking: ranked.map((p) => p.project_id), }); if (submitRes.status !== 200) { diff --git a/server/router/init.go b/server/router/init.go index 4ce4cbb..6131216 100644 --- a/server/router/init.go +++ b/server/router/init.go @@ -87,7 +87,7 @@ func NewRouter(db *mongo.Database) *gin.Engine { judgeRouter.POST("/judge/skip", JudgeSkip) judgeRouter.POST("/judge/score", JudgeScore) judgeRouter.POST("/judge/rank", JudgeRank) - judgeRouter.POST("/judge/submit_batch_ranking", JudgeSubmitBatchRanking) + judgeRouter.POST("/judge/submit-batch-ranking", JudgeSubmitBatchRanking) judgeRouter.PUT("/judge/score", JudgeUpdateScore) judgeRouter.POST("/judge/break", JudgeBreak) diff --git a/server/router/judge.go b/server/router/judge.go index 69274f0..c7cde88 100644 --- a/server/router/judge.go +++ b/server/router/judge.go @@ -442,7 +442,7 @@ type BatchRankingRequest struct { BatchRanking []primitive.ObjectID `json:"batch_ranking"` } -// POST /judge/submit_batch_ranking - +// POST /judge/submit-batch-ranking - func JudgeSubmitBatchRanking(ctx *gin.Context) { // Get the database from the context db := ctx.MustGet("db").(*mongo.Database) From 293c66731c344a63fb7e16d7dbd2dd2a630382db Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Sun, 20 Oct 2024 13:46:05 +0100 Subject: [PATCH 04/13] =?UTF-8?q?=E2=9C=A8=20Allow=20judging=20to=20be=20e?= =?UTF-8?q?nded=20by=20an=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sets a server config flag to signal judging has been ended. The timer is paused and cannot be unpaused. --- client/src/pages/admin/index.tsx | 23 +++++++++++------- client/src/types.d.ts | 4 ++++ server/database/admin.go | 8 ++++++- server/database/options.go | 7 ++++++ server/models/options.go | 2 ++ server/router/admin.go | 41 +++++++++++++++++++++++++++++++- server/router/init.go | 3 +++ 7 files changed, 77 insertions(+), 11 deletions(-) diff --git a/client/src/pages/admin/index.tsx b/client/src/pages/admin/index.tsx index e215097..bc18676 100644 --- a/client/src/pages/admin/index.tsx +++ b/client/src/pages/admin/index.tsx @@ -48,26 +48,32 @@ const Admin = () => { } getNumJudges(); + checkSubmittedJudges(); async function checkJudgingEnded() { - // const judgingEndedRes = await getRequest('/admin/end_judging') - setJudgingEnded(false) + const judgingEndedRes = await getRequest('/admin/end-judging') + if (judgingEndedRes.status !== 200) { + errorAlert(judgingEndedRes); + return; + } + setJudgingEnded(judgingEndedRes.data?.judging_ended as boolean); } checkJudgingEnded(); }, []); function endJudging() { let confirmed = window.confirm("Are you sure you want to end judging? This cannot be undone.\n" + - "Judges will not be able to request new projects to rank and must submit rankings.") + "Judges will not be able to request new projects to rank and must submit rankings."); if (confirmed) { endJudgingReq().then(success => { if (success) { alert("Judging has now been ended. Judges will be notified and made to submit their rankings. " + - "Wait until all have submitted before recording final results.") - setJudgingEnded(true) + "Wait until all have submitted before recording final results."); + setJudgingEnded(true); checkSubmittedJudges(); } else { - alert("Failed to end judging.") + alert("Failed to end judging."); + setJudgingEnded(false); } }) } @@ -75,9 +81,8 @@ const Admin = () => { async function endJudgingReq() { console.log("Requesting server to end judging.") - // const endJudgingRes = await postRequest('/admin/end_judging', null) - // lucatodo: pause and remove clock - return true + const endJudgingRes = await postRequest('/admin/end-judging', null) + return endJudgingRes.status === 200; } async function checkSubmittedJudges() { diff --git a/client/src/types.d.ts b/client/src/types.d.ts index b6f027e..ce3b8b9 100644 --- a/client/src/types.d.ts +++ b/client/src/types.d.ts @@ -102,6 +102,10 @@ interface RankingBatchSize { rbs: number; } +interface JudgingEnded { + judging_ended: boolean; +} + interface Flag { id: string; judge_id: string; diff --git a/server/database/admin.go b/server/database/admin.go index 27756db..458ea51 100644 --- a/server/database/admin.go +++ b/server/database/admin.go @@ -127,7 +127,13 @@ func UpdateMinViews(db *mongo.Database, minViews int) error { // UpdateRankingBatchSize will update the min views setting func UpdateRankingBatchSize(db *mongo.Database, rankingBatchSize int) error { // Update the min views - println(rankingBatchSize) _, err := db.Collection("options").UpdateOne(context.Background(), gin.H{}, gin.H{"$set": gin.H{"ranking_batch_size": rankingBatchSize}}) return err } + +// SetEndJudging will set the judging_ended flag to true +func SetEndJudging(db *mongo.Database) error { + // Update the min views + _, err := db.Collection("options").UpdateOne(context.Background(), gin.H{}, gin.H{"$set": gin.H{"judging_ended": true}}) + return err +} diff --git a/server/database/options.go b/server/database/options.go index f2cf043..f577ddb 100644 --- a/server/database/options.go +++ b/server/database/options.go @@ -55,3 +55,10 @@ func GetRankingBatchSize(db *mongo.Database) (int64, error) { err := db.Collection("options").FindOne(context.Background(), gin.H{}).Decode(&options) return options.RankingBatchSize, err } + +// GetJudgingEnded gets the judgingEnded flag from the database +func GetJudgingEnded(db *mongo.Database) (bool, error) { + var options models.Options + err := db.Collection("options").FindOne(context.Background(), gin.H{}).Decode(&options) + return options.JudgingEnded, err +} diff --git a/server/models/options.go b/server/models/options.go index 80ce53b..d37812b 100644 --- a/server/models/options.go +++ b/server/models/options.go @@ -11,6 +11,7 @@ type Options struct { MinViews int64 `bson:"min_views" json:"min_views"` Categories []string `bson:"categories" json:"categories"` RankingBatchSize int64 `bson:"ranking_batch_size" json:"ranking_batch_size"` + JudgingEnded bool `bson:"judging_ended" json:"judging_ended"` } func NewOptions() *Options { @@ -22,5 +23,6 @@ func NewOptions() *Options { Clock: *NewClockState(), Categories: []string{"Creativity/Innovation", "Technical Competence/Execution", "Research/Design", "Presentation"}, RankingBatchSize: 8, + JudgingEnded: false, } } diff --git a/server/router/admin.go b/server/router/admin.go index 57db42d..10171b6 100644 --- a/server/router/admin.go +++ b/server/router/admin.go @@ -111,6 +111,13 @@ func UnpauseClock(ctx *gin.Context) { // Get the database from the context db := ctx.MustGet("db").(*mongo.Database) + // Check if judging has ended + judgingEnded, err := database.GetJudgingEnded(db) + if judgingEnded { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Judging has been ended. Clock cannot be unpaused."}) + return + } + // Get the clock from the context clock := ctx.MustGet("clock").(*models.ClockState) @@ -118,7 +125,7 @@ func UnpauseClock(ctx *gin.Context) { clock.Resume() // Save the clock in the database - err := database.UpdateClock(db, clock) + err = database.UpdateClock(db, clock) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error saving clock: " + err.Error()}) return @@ -454,6 +461,38 @@ func GetScores(ctx *gin.Context) { ctx.JSON(http.StatusOK, scores) } +// GET /admin/end-judging - isJudgingEnded returns the value of the judging_ended flag +func isJudgingEnded(ctx *gin.Context) { + db := ctx.MustGet("db").(*mongo.Database) + + // Get judging_ended flag from database + judgingEnded, err := database.GetJudgingEnded(db) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error getting judging_ended flag: " + err.Error()}) + return + } + + ctx.JSON(http.StatusOK, gin.H{"judging_ended": judgingEnded}) +} + +// POST /admin/end-judging - endJudging ends the judging process by setting the judging_ended flag to true +func endJudging(ctx *gin.Context) { + // Get the database from the context + db := ctx.MustGet("db").(*mongo.Database) + + // Save the judging_ended flag in the db + err := database.SetEndJudging(db) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error setting judging_ended: " + err.Error()}) + return + } + + PauseClock(ctx) + + // Send OK + ctx.JSON(http.StatusOK, gin.H{"ok": 1}) +} + // contains checks if a string is in a list of strings func contains(list []primitive.ObjectID, str primitive.ObjectID) bool { for _, s := range list { diff --git a/server/router/init.go b/server/router/init.go index 6131216..6cb8c55 100644 --- a/server/router/init.go +++ b/server/router/init.go @@ -133,6 +133,9 @@ func NewRouter(db *mongo.Database) *gin.Engine { judgeRouter.POST("/judge/notes", JudgeUpdateNotes) judgeRouter.GET("/rbs", GetRankingBatchSize) + adminRouter.GET("/admin/end-judging", isJudgingEnded) + adminRouter.POST("/admin/end-judging", endJudging) + // Serve frontend static files router.Use(static.Serve("/assets", static.LocalFile("./public/assets", true))) router.StaticFile("/favicon.ico", "./public/favicon.ico") From 1b6edd3c1032ef48a4e2e1efa9e50c66608c2b1d Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Sun, 20 Oct 2024 13:47:18 +0100 Subject: [PATCH 05/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20small=20Go=20f?= =?UTF-8?q?unction=20doc=20typos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/router/admin.go | 2 +- server/router/judge.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/router/admin.go b/server/router/admin.go index 10171b6..fb4419b 100644 --- a/server/router/admin.go +++ b/server/router/admin.go @@ -413,7 +413,7 @@ func SetRankingBatchSize(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"ok": 1}) } -// /GET /admin/score - GetScores returns the calculated scores of all projects +// GET /admin/score - GetScores returns the calculated scores of all projects func GetScores(ctx *gin.Context) { // Get the database from the context db := ctx.MustGet("db").(*mongo.Database) diff --git a/server/router/judge.go b/server/router/judge.go index c7cde88..8646fc1 100644 --- a/server/router/judge.go +++ b/server/router/judge.go @@ -521,7 +521,7 @@ func GetRankingBatchSize(ctx *gin.Context) { // Get the database from the context db := ctx.MustGet("db").(*mongo.Database) - // Get categories from database + // Get ranking batch size from database rbs, err := database.GetRankingBatchSize(db) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error getting ranking batch size: " + err.Error()}) From 3567c2e5a6e2ff91b5be3c76d72565040da1be9c Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Sun, 20 Oct 2024 14:01:27 +0100 Subject: [PATCH 06/13] =?UTF-8?q?=E2=9A=B0=20Remove=20old=20client=20req?= =?UTF-8?q?=20error=20handling=20and=20rename=20OkResponse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old client error handling handled displayed all errors as 404s: `return { status: 404, error: error, data: null };` This made it very difficult to debug server-side errors. This has now been removed so errors are directly shown to the user with the right status code. Also use the much clearer term YesNoResponse. Previously there were lines like this: `ctx.JSON(http.StatusOK, gin.H{"ok": 0})`` So both okay and not okay. These lines are now: `ctx.JSON(http.StatusOK, gin.H{"yes_no": 0})` --- client/src/api.ts | 6 +--- .../src/components/admin/tables/HidePopup.tsx | 2 +- .../src/components/admin/tables/JudgeRow.tsx | 2 +- .../components/admin/tables/ProjectRow.tsx | 2 +- client/src/components/judge/Ratings.tsx | 4 +-- .../src/components/judge/popups/FlagPopup.tsx | 2 +- client/src/pages/admin/index.tsx | 4 +-- client/src/pages/admin/settings.tsx | 28 ++++++++--------- client/src/pages/judge/index.tsx | 14 ++++----- client/src/pages/judge/live.tsx | 14 ++++----- client/src/pages/judge/project.tsx | 2 +- client/src/pages/judge/welcome.tsx | 6 ++-- client/src/types.d.ts | 4 +-- server/router/admin.go | 20 ++++++------- server/router/judge.go | 30 +++++++++---------- server/router/project.go | 20 ++++++------- 16 files changed, 78 insertions(+), 82 deletions(-) diff --git a/client/src/api.ts b/client/src/api.ts index fca564a..6d1398c 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -8,7 +8,6 @@ export async function getRequest(path: string): Promise> { credentials: 'include', }; const response = await fetch(`${BACKEND_URL}${path}`, options); - if (!response.ok) throw new Error(response.statusText); const data = await response.json(); return { status: response.status, error: data.error ? data.error : '', data }; @@ -31,7 +30,6 @@ export async function postRequest( body: body ? JSON.stringify(body) : null, }; const response = await fetch(`${BACKEND_URL}${path}`, options); - if (!response.ok) throw new Error(response.statusText); const data = await response.json(); return { status: response.status, error: data.error ? data.error : '', data }; @@ -54,7 +52,6 @@ export async function putRequest( body: body ? JSON.stringify(body) : null, }; const response = await fetch(`${BACKEND_URL}${path}`, options); - if (!response.ok) throw new Error(response.statusText); const data = await response.json(); return { status: response.status, error: data.error ? data.error : '', data }; @@ -67,7 +64,7 @@ export async function putRequest( export async function deleteRequest( path: string -): Promise> { +): Promise> { try { const options: RequestInit = { method: 'DELETE', @@ -75,7 +72,6 @@ export async function deleteRequest( credentials: 'include', }; const response = await fetch(`${BACKEND_URL}${path}`, options); - if (!response.ok) throw new Error(response.statusText); const data = await response.json(); return { status: response.status, error: data.error ? data.error : '', data }; diff --git a/client/src/components/admin/tables/HidePopup.tsx b/client/src/components/admin/tables/HidePopup.tsx index 48acdf0..eaf19b4 100644 --- a/client/src/components/admin/tables/HidePopup.tsx +++ b/client/src/components/admin/tables/HidePopup.tsx @@ -23,7 +23,7 @@ const DeletePopup = ({ element, close }: DeletePopupProps) => { const hideElement = async () => { const resource = isProject(element) ? 'project' : 'judge'; - const res = await postRequest(`/${resource}/hide`, {id: element.id}); + const res = await postRequest(`/${resource}/hide`, {id: element.id}); if (res.status === 200) { fetchStats(); isProject(element) ? fetchProjects() : fetchJudges(); diff --git a/client/src/components/admin/tables/JudgeRow.tsx b/client/src/components/admin/tables/JudgeRow.tsx index 199c4c4..6976ee8 100644 --- a/client/src/components/admin/tables/JudgeRow.tsx +++ b/client/src/components/admin/tables/JudgeRow.tsx @@ -43,7 +43,7 @@ const JudgeRow = ({ judge, idx, checked, handleCheckedChange }: JudgeRowProps) = }; const hideJudge = async () => { - const res = await postRequest(judge.active ? '/judge/hide' : '/judge/unhide', {id: judge.id}); + const res = await postRequest(judge.active ? '/judge/hide' : '/judge/unhide', {id: judge.id}); if (res.status === 200) { alert(`Judge ${judge.active ? 'hidden' : 'un-hidden'} successfully!`); fetchJudges(); diff --git a/client/src/components/admin/tables/ProjectRow.tsx b/client/src/components/admin/tables/ProjectRow.tsx index 9c6ba07..724f2b1 100644 --- a/client/src/components/admin/tables/ProjectRow.tsx +++ b/client/src/components/admin/tables/ProjectRow.tsx @@ -54,7 +54,7 @@ const ProjectRow = ({ project, idx, checked, handleCheckedChange }: ProjectRowPr }; const hideProject = async () => { - const res = await postRequest(project.active ? '/project/hide' : '/project/unhide', {id: project.id}); + const res = await postRequest(project.active ? '/project/hide' : '/project/unhide', {id: project.id}); if (res.status === 200) { alert(`Project ${project.active ? 'hidden' : 'un-hidden'} successfully!`); fetchProjects(); diff --git a/client/src/components/judge/Ratings.tsx b/client/src/components/judge/Ratings.tsx index db7bb64..72a7c17 100644 --- a/client/src/components/judge/Ratings.tsx +++ b/client/src/components/judge/Ratings.tsx @@ -54,11 +54,11 @@ const Ratings = (props: RatingsProps) => { // Score the current project const scoreRes = props.update - ? await putRequest('/judge/score', { + ? await putRequest('/judge/score', { categories: scores, project: props.project?.project_id, }) - : await postRequest('/judge/score', { + : await postRequest('/judge/score', { categories: scores, initial: true, }); diff --git a/client/src/components/judge/popups/FlagPopup.tsx b/client/src/components/judge/popups/FlagPopup.tsx index ad68e17..e9283ca 100644 --- a/client/src/components/judge/popups/FlagPopup.tsx +++ b/client/src/components/judge/popups/FlagPopup.tsx @@ -66,7 +66,7 @@ const FlagPopup = (props: FlagPopupProps) => { } // Flag the current project - const flagRes = await postRequest('/judge/skip', { + const flagRes = await postRequest('/judge/skip', { reason: selected, }); if (flagRes.status !== 200) { diff --git a/client/src/pages/admin/index.tsx b/client/src/pages/admin/index.tsx index bc18676..7af8e88 100644 --- a/client/src/pages/admin/index.tsx +++ b/client/src/pages/admin/index.tsx @@ -23,7 +23,7 @@ const Admin = () => { useEffect(() => { // Check if user logged in async function checkLoggedIn() { - const loggedInRes = await postRequest('/admin/auth', null); + const loggedInRes = await postRequest('/admin/auth', null); if (loggedInRes.status === 401) { console.error(`Admin is not logged in!`); navigate('/'); @@ -81,7 +81,7 @@ const Admin = () => { async function endJudgingReq() { console.log("Requesting server to end judging.") - const endJudgingRes = await postRequest('/admin/end-judging', null) + const endJudgingRes = await postRequest('/admin/end-judging', null) return endJudgingRes.status === 200; } diff --git a/client/src/pages/admin/settings.tsx b/client/src/pages/admin/settings.tsx index c2f657e..1f9033f 100644 --- a/client/src/pages/admin/settings.tsx +++ b/client/src/pages/admin/settings.tsx @@ -65,8 +65,8 @@ const AdminSettings = () => { }, []); const reassignTables = async () => { - const res = await postRequest('/project/reassign', null); - if (res.status !== 200 || res.data?.ok !== 1) { + const res = await postRequest('/project/reassign', null); + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } @@ -90,10 +90,10 @@ const AdminSettings = () => { } // Update the timer - const res = await postRequest('/admin/timer', { + const res = await postRequest('/admin/timer', { judging_timer: timer, }); - if (res.status !== 200 || res.data?.ok !== 1) { + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } @@ -111,10 +111,10 @@ const AdminSettings = () => { } // Update min views - const res = await postRequest('/admin/min-views', { + const res = await postRequest('/admin/min-views', { min_views: v, }); - if (res.status !== 200 || res.data?.ok !== 1) { + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } @@ -135,10 +135,10 @@ const AdminSettings = () => { return } // Post the new categories - const res = await postRequest('/admin/categories', { + const res = await postRequest('/admin/categories', { categories: filteredCats, }); - if (res.status !== 200 || res.data?.ok !== 1) { + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } @@ -154,10 +154,10 @@ const AdminSettings = () => { alert('Minimum views should be a positive integer >= 2!'); return; } - const res = await postRequest('/admin/ranking-batch-size', { + const res = await postRequest('/admin/ranking-batch-size', { ranking_batch_size: r, }); - if (res.status !== 200 || res.data?.ok !== 1) { + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } @@ -167,8 +167,8 @@ const AdminSettings = () => { } const resetClock = async () => { - const res = await postRequest('/admin/clock/reset', null); - if (res.status !== 200 || res.data?.ok !== 1) { + const res = await postRequest('/admin/clock/reset', null); + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } @@ -178,8 +178,8 @@ const AdminSettings = () => { }; const dropDatabase = async () => { - const res = await postRequest('/admin/reset', null); - if (res.status !== 200 || res.data?.ok !== 1) { + const res = await postRequest('/admin/reset', null); + if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } diff --git a/client/src/pages/judge/index.tsx b/client/src/pages/judge/index.tsx index dfce896..74bea10 100644 --- a/client/src/pages/judge/index.tsx +++ b/client/src/pages/judge/index.tsx @@ -52,7 +52,7 @@ const Judge = () => { useEffect(() => { async function fetchData() { // Check to see if the user is logged in - const loggedInRes = await postRequest('/judge/auth', null); + const loggedInRes = await postRequest('/judge/auth', null); if (loggedInRes.status === 401) { console.error(`Judge is not logged in!`); navigate('/'); @@ -62,19 +62,19 @@ const Judge = () => { errorAlert(loggedInRes); return; } - if (loggedInRes.data?.ok !== 1) { + if (loggedInRes.data?.yes_no !== 1) { console.error(`Judge is not logged in!`); navigate('/'); return; } // Check for read welcome - const readWelcomeRes = await getRequest('/judge/welcome'); + const readWelcomeRes = await getRequest('/judge/welcome'); if (readWelcomeRes.status !== 200) { errorAlert(readWelcomeRes); return; } - const readWelcome = readWelcomeRes.data?.ok === 1; + const readWelcome = readWelcomeRes.data?.yes_no === 1; if (!readWelcome) { navigate('/judge/welcome'); } @@ -158,7 +158,7 @@ const Judge = () => { return; } - const res = await postRequest('/judge/break', null); + const res = await postRequest('/judge/break', null); if (res.status !== 200) { errorAlert(res); return; @@ -257,7 +257,7 @@ const Judge = () => { const saveSort = async (projects: SortableJudgedProject[]) => { // Save the rankings - const saveRes = await postRequest('/judge/rank', { + const saveRes = await postRequest('/judge/rank', { ranking: projects.map((p) => p.project_id), }); if (saveRes.status !== 200) { @@ -271,7 +271,7 @@ const Judge = () => { alert(`You can only submit rankings in batches of ${rankingBatchSize} projects.`) return } - const submitRes = await postRequest('/judge/submit-batch-ranking', { + const submitRes = await postRequest('/judge/submit-batch-ranking', { batch_ranking: ranked.map((p) => p.project_id), }); if (submitRes.status !== 200) { diff --git a/client/src/pages/judge/live.tsx b/client/src/pages/judge/live.tsx index 6cc7d31..1969d8e 100644 --- a/client/src/pages/judge/live.tsx +++ b/client/src/pages/judge/live.tsx @@ -51,35 +51,35 @@ const JudgeLive = () => { useEffect(() => { async function fetchData() { // Check to see if the user is logged in - const loggedInRes = await postRequest('/judge/auth', null); + const loggedInRes = await postRequest('/judge/auth', null); if (loggedInRes.status !== 200) { errorAlert(loggedInRes); return; } - if (loggedInRes.data?.ok !== 1) { + if (loggedInRes.data?.yes_no !== 1) { console.error(`Judge is not logged in!`); navigate('/'); return; } // Check for read welcome - const readWelcomeRes = await getRequest('/judge/welcome'); + const readWelcomeRes = await getRequest('/judge/welcome'); if (readWelcomeRes.status !== 200) { errorAlert(readWelcomeRes); return; } - const readWelcome = readWelcomeRes.data?.ok === 1; + const readWelcome = readWelcomeRes.data?.yes_no === 1; if (!readWelcome) { navigate('/judge/welcome'); } // Check to see if judging has started - const startedRes = await getRequest('/admin/started'); + const startedRes = await getRequest('/admin/started'); if (startedRes.status !== 200) { errorAlert(startedRes); return; } - if (startedRes.data?.ok !== 1) { + if (startedRes.data?.yes_no !== 1) { setVerified(true); setInfoPage('paused'); return; @@ -231,7 +231,7 @@ const JudgeLive = () => { // Update notes if voting if (isVote) { - const res = await postRequest('/judge/notes', { + const res = await postRequest('/judge/notes', { notes, project: judge?.current, }); diff --git a/client/src/pages/judge/project.tsx b/client/src/pages/judge/project.tsx index e052bd0..4994f03 100644 --- a/client/src/pages/judge/project.tsx +++ b/client/src/pages/judge/project.tsx @@ -33,7 +33,7 @@ const Project = () => { if (!project) return; async function updateNotes() { - const res = await postRequest('/judge/notes', { + const res = await postRequest('/judge/notes', { notes, project: project?.project_id, }); diff --git a/client/src/pages/judge/welcome.tsx b/client/src/pages/judge/welcome.tsx index c638079..b214ef1 100644 --- a/client/src/pages/judge/welcome.tsx +++ b/client/src/pages/judge/welcome.tsx @@ -18,12 +18,12 @@ const JudgeWelcome = () => { useEffect(() => { async function fetchData() { // Check to see if the user is logged in - const loggedInRes = await postRequest('/judge/auth', null); + const loggedInRes = await postRequest('/judge/auth', null); if (loggedInRes.status !== 200) { errorAlert(loggedInRes); return; } - if (loggedInRes.data?.ok !== 1) { + if (loggedInRes.data?.yes_no !== 1) { console.error(`Judge is not logged in!`); navigate('/'); return; @@ -51,7 +51,7 @@ const JudgeWelcome = () => { } // POST to server to mark that the user has read the welcome message - const readWelcomeRes = await postRequest('/judge/welcome', null); + const readWelcomeRes = await postRequest('/judge/welcome', null); if (readWelcomeRes.status !== 200) { errorAlert(readWelcomeRes); return; diff --git a/client/src/types.d.ts b/client/src/types.d.ts index ce3b8b9..7cdda21 100644 --- a/client/src/types.d.ts +++ b/client/src/types.d.ts @@ -64,8 +64,8 @@ interface VotingProjectInfo { prev_location: number; } -interface OkResponse { - ok: number; +interface YesNoResponse { + yes_no: number; } interface TokenResponse { diff --git a/server/router/admin.go b/server/router/admin.go index fb4419b..104cbe4 100644 --- a/server/router/admin.go +++ b/server/router/admin.go @@ -33,7 +33,7 @@ func LoginAdmin(ctx *gin.Context) { // Return status OK if the password matches if req.Password == password { - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) return } @@ -46,7 +46,7 @@ func AdminAuthenticated(ctx *gin.Context) { // This route will run the middleware first, and if the middleware // passes, then that means the admin is authenticated - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // GET /admin/stats - GetAdminStats returns stats about the system @@ -163,9 +163,9 @@ func IsClockPaused(ctx *gin.Context) { // Send OK if clock.Running { - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } else { - ctx.JSON(http.StatusOK, gin.H{"ok": 0}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 0}) } } @@ -182,7 +182,7 @@ func ResetDatabase(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /admin/flags - GetFlags returns all flags @@ -328,7 +328,7 @@ func SetJudgingTimer(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type SetCategoriesRequest struct { @@ -356,7 +356,7 @@ func SetCategories(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type MinViewsRequest struct { @@ -387,7 +387,7 @@ func SetMinViews(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /admin/ranking-batch-size - sets the ranking batch size @@ -410,7 +410,7 @@ func SetRankingBatchSize(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // GET /admin/score - GetScores returns the calculated scores of all projects @@ -490,7 +490,7 @@ func endJudging(ctx *gin.Context) { PauseClock(ctx) // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // contains checks if a string is in a list of strings diff --git a/server/router/judge.go b/server/router/judge.go index 8646fc1..34182dd 100644 --- a/server/router/judge.go +++ b/server/router/judge.go @@ -32,7 +32,7 @@ func GetJudge(ctx *gin.Context) { func JudgeAuthenticated(ctx *gin.Context) { // This route will run the middleware first, and if the middleware // passes, then that means the judge is authenticated - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // GET /judge/welcome - Endpoint to check if a judge has read the welcome message @@ -42,9 +42,9 @@ func CheckJudgeReadWelcome(ctx *gin.Context) { // Send OK if judge.ReadWelcome { - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } else { - ctx.JSON(http.StatusOK, gin.H{"ok": 0}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 0}) } } @@ -67,7 +67,7 @@ func SetJudgeReadWelcome(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // GET /judge/list - Endpoint to get a list of all judges @@ -125,7 +125,7 @@ func DeleteJudge(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /judge/next - Endpoint to get the next project for a judge @@ -266,7 +266,7 @@ func JudgeSkip(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /judge/hide - Endpoint to hide a judge @@ -297,7 +297,7 @@ func HideJudge(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /judge/unhide - Endpoint to unhide a judge @@ -329,7 +329,7 @@ func UnhideJudge(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // PUT /judge/:id - Endpoint to edit a judge @@ -363,7 +363,7 @@ func EditJudge(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type JudgeScoreRequest struct { @@ -404,7 +404,7 @@ func JudgeScore(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type RankRequest struct { @@ -435,7 +435,7 @@ func JudgeRank(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type BatchRankingRequest struct { @@ -469,7 +469,7 @@ func JudgeSubmitBatchRanking(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /judge/break - Allows a judge to take a break and free up their current project @@ -497,7 +497,7 @@ func JudgeBreak(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // GET /categories - Endpoint to get the categories @@ -586,7 +586,7 @@ func JudgeUpdateScore(ctx *gin.Context) { comps.UpdateProjectComparisonCount(judge.SeenProjects, scoreReq.Project) // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type UpdateNotesRequest struct { @@ -637,5 +637,5 @@ func JudgeUpdateNotes(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } diff --git a/server/router/project.go b/server/router/project.go index c9950c3..1db4f02 100644 --- a/server/router/project.go +++ b/server/router/project.go @@ -56,7 +56,7 @@ func AddDevpostCsv(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } type AddProjectRequest struct { @@ -127,7 +127,7 @@ func AddProject(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // GET /project/list - ListProjects lists all projects in the database @@ -230,7 +230,7 @@ func AddProjectsCsv(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // DELETE /project/:id - DeleteProject deletes a project from the database @@ -256,7 +256,7 @@ func DeleteProject(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /project/stats - ProjectStats returns stats about projects @@ -346,7 +346,7 @@ func HideProject(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /project/unhide - UnhideProject unhides a project @@ -378,7 +378,7 @@ func UnhideProject(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /project/prioritize - PrioritizeProject prioritizes a project @@ -410,7 +410,7 @@ func PrioritizeProject(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } // POST /project/unprioritize - UnprioritizeProject unprioritizes a project @@ -442,7 +442,7 @@ func UnprioritizeProject(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } func ReassignProjectNums(ctx *gin.Context) { @@ -458,7 +458,7 @@ func ReassignProjectNums(ctx *gin.Context) { // If projects is empty, send OK if len(projects) == 0 { - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) return } @@ -495,5 +495,5 @@ func ReassignProjectNums(ctx *gin.Context) { } // Send OK - ctx.JSON(http.StatusOK, gin.H{"ok": 1}) + ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } From 204444fe356e82f2978cfde2b5b8a8db9d35b7cf Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:39:42 +0100 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=97=91=20Change=20custom=20return?= =?UTF-8?q?=20type=20from=20judge=20ending=20check=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding a whole new return type for this was unnecessary. I can just use the yes-no response I set up last time. --- client/src/pages/admin/index.tsx | 4 ++-- client/src/types.d.ts | 4 ---- server/router/admin.go | 10 ++++++++-- server/router/init.go | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/src/pages/admin/index.tsx b/client/src/pages/admin/index.tsx index 7af8e88..cb42066 100644 --- a/client/src/pages/admin/index.tsx +++ b/client/src/pages/admin/index.tsx @@ -51,12 +51,12 @@ const Admin = () => { checkSubmittedJudges(); async function checkJudgingEnded() { - const judgingEndedRes = await getRequest('/admin/end-judging') + const judgingEndedRes = await getRequest('/check-judging-over') if (judgingEndedRes.status !== 200) { errorAlert(judgingEndedRes); return; } - setJudgingEnded(judgingEndedRes.data?.judging_ended as boolean); + setJudgingEnded(Boolean(judgingEndedRes.data?.yes_no)); } checkJudgingEnded(); }, []); diff --git a/client/src/types.d.ts b/client/src/types.d.ts index 7cdda21..a178178 100644 --- a/client/src/types.d.ts +++ b/client/src/types.d.ts @@ -102,10 +102,6 @@ interface RankingBatchSize { rbs: number; } -interface JudgingEnded { - judging_ended: boolean; -} - interface Flag { id: string; judge_id: string; diff --git a/server/router/admin.go b/server/router/admin.go index 104cbe4..523277a 100644 --- a/server/router/admin.go +++ b/server/router/admin.go @@ -461,7 +461,7 @@ func GetScores(ctx *gin.Context) { ctx.JSON(http.StatusOK, scores) } -// GET /admin/end-judging - isJudgingEnded returns the value of the judging_ended flag +// GET /check-judging-over - isJudgingEnded returns the value of the judging_ended flag func isJudgingEnded(ctx *gin.Context) { db := ctx.MustGet("db").(*mongo.Database) @@ -472,7 +472,13 @@ func isJudgingEnded(ctx *gin.Context) { return } - ctx.JSON(http.StatusOK, gin.H{"judging_ended": judgingEnded}) + // no type conversion from bool to int directly :'( : https://stackoverflow.com/a/38627381/7253717 + var judgingEndedVar int8 + if judgingEnded { + judgingEndedVar = 1 + } + + ctx.JSON(http.StatusOK, gin.H{"yes_no": judgingEndedVar}) } // POST /admin/end-judging - endJudging ends the judging process by setting the judging_ended flag to true diff --git a/server/router/init.go b/server/router/init.go index 6cb8c55..8ef773c 100644 --- a/server/router/init.go +++ b/server/router/init.go @@ -133,7 +133,7 @@ func NewRouter(db *mongo.Database) *gin.Engine { judgeRouter.POST("/judge/notes", JudgeUpdateNotes) judgeRouter.GET("/rbs", GetRankingBatchSize) - adminRouter.GET("/admin/end-judging", isJudgingEnded) + defaultRouter.GET("/check-judging-over", isJudgingEnded) adminRouter.POST("/admin/end-judging", endJudging) // Serve frontend static files From 397624b69475d709700ca9edc82fcf41aecfbf31 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:06:23 +0100 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=90=9B=20Fix=20clock=20status=20bei?= =?UTF-8?q?ng=20added=20to=20return=20JSON=20for=20judge=20ending?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling the PauseClock() function inside of endJudging() was attending clock data to the JSON return incorrectly. --- server/router/admin.go | 27 ++++++++++++++++++++------- server/router/init.go | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/server/router/admin.go b/server/router/admin.go index 523277a..707ba7c 100644 --- a/server/router/admin.go +++ b/server/router/admin.go @@ -84,8 +84,7 @@ func GetClock(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"running": clock.Running, "time": clock.GetDuration()}) } -// POST /admin/clock/pause - PauseClock pauses the clock -func PauseClock(ctx *gin.Context) { +func PauseClock(ctx *gin.Context) *models.ClockState { // Get the database from the context db := ctx.MustGet("db").(*mongo.Database) @@ -99,11 +98,19 @@ func PauseClock(ctx *gin.Context) { err := database.UpdateClock(db, clock) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error saving clock: " + err.Error()}) - return + return nil } + return clock +} - // Send OK - ctx.JSON(http.StatusOK, gin.H{"clock": clock}) +// POST /admin/clock/pause - PauseClockHandler pauses the clock +func PauseClockHandler(ctx *gin.Context) { + clock := PauseClock(ctx) + + if clock != nil { + // Send OK + ctx.JSON(http.StatusOK, gin.H{"clock": clock}) + } } // POST /admin/clock/unpause - UnpauseClock unpauses the clock @@ -486,6 +493,14 @@ func endJudging(ctx *gin.Context) { // Get the database from the context db := ctx.MustGet("db").(*mongo.Database) + // Pause the clock + clock := PauseClock(ctx) + + if clock == nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error pausing clock"}) + return + } + // Save the judging_ended flag in the db err := database.SetEndJudging(db) if err != nil { @@ -493,8 +508,6 @@ func endJudging(ctx *gin.Context) { return } - PauseClock(ctx) - // Send OK ctx.JSON(http.StatusOK, gin.H{"yes_no": 1}) } diff --git a/server/router/init.go b/server/router/init.go index 8ef773c..6d5ac9e 100644 --- a/server/router/init.go +++ b/server/router/init.go @@ -105,7 +105,7 @@ func NewRouter(db *mongo.Database) *gin.Engine { adminRouter.GET("/admin/stats", GetAdminStats) adminRouter.GET("/admin/score", GetScores) adminRouter.GET("/admin/clock", GetClock) - adminRouter.POST("/admin/clock/pause", PauseClock) + adminRouter.POST("/admin/clock/pause", PauseClockHandler) adminRouter.POST("/admin/clock/unpause", UnpauseClock) adminRouter.POST("/admin/clock/reset", ResetClock) adminRouter.POST("/admin/auth", AdminAuthenticated) From c176a278c27f4323d8d8251416d0158eeb45b3b9 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:29:59 +0100 Subject: [PATCH 09/13] =?UTF-8?q?=E2=9C=A8=20Handle=20judging=20ending=20o?= =?UTF-8?q?n=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend for judges now updates when judging is ended. Judges are made to finish ranking and submit their batches. --- client/src/data.json | 4 ++++ client/src/pages/judge/index.tsx | 39 ++++++++++++++++++++++++-------- client/src/pages/judge/live.tsx | 14 +++++++++++- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/client/src/data.json b/client/src/data.json index 7f6ceb9..13e004c 100644 --- a/client/src/data.json +++ b/client/src/data.json @@ -15,6 +15,10 @@ "noProjects": { "title": "There are no projects to judge", "description": "You're early! There seems to be no projects currently in the system to judge. Please let an organizer know if this is unexpected." + }, + "judgingEnded": { + "title": "Judging has been ended", + "description": "Judging has been ended by an admin. Please rank and submit what projects you have already seen." } }, "flags": { diff --git a/client/src/pages/judge/index.tsx b/client/src/pages/judge/index.tsx index 74bea10..2d827c9 100644 --- a/client/src/pages/judge/index.tsx +++ b/client/src/pages/judge/index.tsx @@ -31,6 +31,7 @@ const Judge = () => { const [unranked, setUnranked] = useState([]); const [allRanked, setAllRanked] = useState(false) const [rankingBatchSize, setRankingBatchSize] = useState(0); + const [judgingIsOver, setJudgingIsOver] = useState(false); const [nextButtonDisabled, setNextButtonDisabled] = useState(false); const [nextButtonHelperText, setNextButtonHelperText] = useState(''); const [loaded, setLoaded] = useState(false); @@ -103,6 +104,15 @@ const Judge = () => { return; } setRankingBatchSize(rankingBatchSizeRes.data?.rbs as number); + + const judgingEndedRes = await getRequest('/check-judging-over') + if (judgingEndedRes.status !== 200) { + errorAlert(judgingEndedRes); + return; + } + let judgingIsOver = Boolean(judgingEndedRes.data?.yes_no) + setJudgingIsOver(judgingIsOver); + setNextButtonDisabled(judgingIsOver); } fetchData(); @@ -136,17 +146,17 @@ const Judge = () => { // Trigger button state ranking batch logic updates when `rankingBatchSize` is set (>0) and/or whenever `ranked` or `unranked` states chance useEffect(() => { if (rankingBatchSize > 0) { - setAllRanked(ranked.length === rankingBatchSize && unranked.length === 0); + setAllRanked((ranked.length === rankingBatchSize || judgingIsOver) && unranked.length === 0); if (ranked.length + unranked.length === rankingBatchSize) { setNextButtonHelperText('Rank and submit your current batch to move on'); setNextButtonDisabled(true); } else { setNextButtonHelperText(''); - setNextButtonDisabled(false); + if (!judgingIsOver) setNextButtonDisabled(false); } } - }, [rankingBatchSize, ranked, unranked, loaded]); + }, [rankingBatchSize, judgingIsOver, ranked, unranked, loaded]); if (!loaded) return ; @@ -267,10 +277,15 @@ const Judge = () => { }; const submitBatch = async () => { - if (ranked.length !== rankingBatchSize) { // lucatodo: handle end of event + if (ranked.length !== rankingBatchSize && !judgingIsOver) { alert(`You can only submit rankings in batches of ${rankingBatchSize} projects.`) return } + if (ranked.length === 0) { + alert('You cannot submit an empty batch.') + return + } + const submitRes = await postRequest('/judge/submit-batch-ranking', { batch_ranking: ranked.map((p) => p.project_id), }); @@ -287,6 +302,10 @@ const Judge = () => { <> +

Welcome, {judge?.name}!

-
- - + + +
{
{/* lucatodo: text updates if judging is ended manually to allow 'early' submission (see issue #4) */} Please rank all your projects to submit.
- You can only submit rankings in batches of {rankingBatchSize} projects. +
diff --git a/client/src/pages/judge/live.tsx b/client/src/pages/judge/live.tsx index 1969d8e..614205b 100644 --- a/client/src/pages/judge/live.tsx +++ b/client/src/pages/judge/live.tsx @@ -18,12 +18,13 @@ import alarm from '../../assets/alarm.mp3'; import data from '../../data.json'; import RawTextInput from '../../components/RawTextInput'; -const infoPages = ['paused', 'hidden', 'no-projects', 'done']; +const infoPages = ['paused', 'hidden', 'no-projects', 'done', 'judging-ended']; const infoData = [ data.judgeInfo.paused, data.judgeInfo.hidden, data.judgeInfo.noProjects, data.judgeInfo.done, + data.judgeInfo.judgingEnded, ]; const audio = new Audio(alarm); @@ -73,6 +74,17 @@ const JudgeLive = () => { navigate('/judge/welcome'); } + const judgingEndedRes = await getRequest('/check-judging-over') + if (judgingEndedRes.status !== 200) { + errorAlert(judgingEndedRes); + return; + } + if (judgingEndedRes.data?.yes_no === 1) { + setVerified(true) + setInfoPage('judging-ended'); + return; + } + // Check to see if judging has started const startedRes = await getRequest('/admin/started'); if (startedRes.status !== 200) { From 5a46f92bc528dbb76239a1c6b18709a075aed149 Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:32:16 +0100 Subject: [PATCH 10/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20judging?= =?UTF-8?q?=20ended=20variables=20on=20admin=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For consistency with judge page. Also update old docstring of isJudgingEnded function. --- client/src/pages/admin/index.tsx | 14 +++++++------- server/router/admin.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/pages/admin/index.tsx b/client/src/pages/admin/index.tsx index cb42066..be0601d 100644 --- a/client/src/pages/admin/index.tsx +++ b/client/src/pages/admin/index.tsx @@ -16,7 +16,7 @@ const Admin = () => { const navigate = useNavigate(); const [showProjects, setShowProjects] = useState(true); const [loading, setLoading] = useState(true); - const [judgingEnded, setJudgingEnded] = useState(false); + const [judgingIsOver, setJudgingIsOver] = useState(false); const [numJudges, setNumJudges] = useState(0); const [submittedJudges, setSubmittedJudges] = useState(0); @@ -56,7 +56,7 @@ const Admin = () => { errorAlert(judgingEndedRes); return; } - setJudgingEnded(Boolean(judgingEndedRes.data?.yes_no)); + setJudgingIsOver(Boolean(judgingEndedRes.data?.yes_no)); } checkJudgingEnded(); }, []); @@ -69,11 +69,11 @@ const Admin = () => { if (success) { alert("Judging has now been ended. Judges will be notified and made to submit their rankings. " + "Wait until all have submitted before recording final results."); - setJudgingEnded(true); + setJudgingIsOver(true); checkSubmittedJudges(); } else { alert("Failed to end judging."); - setJudgingEnded(false); + setJudgingIsOver(false); } }) } @@ -120,11 +120,11 @@ const Admin = () => { - + >{judgingIsOver ? `Submitted judges: ${submittedJudges}/${numJudges}` : "End Judging"} +
diff --git a/server/router/admin.go b/server/router/admin.go index 707ba7c..f827e79 100644 --- a/server/router/admin.go +++ b/server/router/admin.go @@ -468,7 +468,7 @@ func GetScores(ctx *gin.Context) { ctx.JSON(http.StatusOK, scores) } -// GET /check-judging-over - isJudgingEnded returns the value of the judging_ended flag +// GET /check-judging-over - isJudgingEnded returns a yes_no indicating the value of the judging_ended boolean flag func isJudgingEnded(ctx *gin.Context) { db := ctx.MustGet("db").(*mongo.Database) From 0a722bafc184c4c0d78188c5b013019011fdcbff Mon Sep 17 00:00:00 2001 From: Luca <30503695+tameTNT@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:08:53 +0100 Subject: [PATCH 11/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20RBS=20to=20?= =?UTF-8?q?BRS=20for=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new standard across the codebase is (hopefully) Batch Ranking Size (BRS) --- client/src/pages/admin/settings.tsx | 30 ++++++++++++++--------------- client/src/pages/judge/index.tsx | 28 +++++++++++++-------------- client/src/types.d.ts | 6 +++--- server/database/admin.go | 6 +++--- server/database/options.go | 6 +++--- server/models/options.go | 4 ++-- server/router/admin.go | 14 +++++++------- server/router/init.go | 6 ++++-- server/router/judge.go | 6 +++--- 9 files changed, 54 insertions(+), 52 deletions(-) diff --git a/client/src/pages/admin/settings.tsx b/client/src/pages/admin/settings.tsx index 1f9033f..ccbf18e 100644 --- a/client/src/pages/admin/settings.tsx +++ b/client/src/pages/admin/settings.tsx @@ -24,7 +24,7 @@ const AdminSettings = () => { const [judgingTimer, setJudgingTimer] = useState(''); const [minViews, setMinViews] = useState(''); const [categories, setCategories] = useState(''); - const [rankingBatchSize, setRankingBatchSize] = useState(''); + const [batchRankingSize, setBatchRankingSize] = useState(''); const [loading, setLoading] = useState(true); async function getOptions() { @@ -54,8 +54,8 @@ const AdminSettings = () => { // Set min views setMinViews(res.data.min_views.toString()); - // Set ranking batch size - setRankingBatchSize(res.data.ranking_batch_size.toString()) + // Set batch ranking size + setBatchRankingSize(res.data.batch_ranking_size.toString()) setLoading(false); } @@ -147,22 +147,22 @@ const AdminSettings = () => { getOptions(); }; - const updateRankingBatchSize = async () => { - // Convert rankingBatchSize to integer - const r = parseInt(rankingBatchSize); + const updateBatchRankingSize = async () => { + // Convert batchRankingSize to integer + const r = parseInt(batchRankingSize); if (isNaN(r) || r < 2) { - alert('Minimum views should be a positive integer >= 2!'); + alert('Minimum batch ranking size should be a positive integer >= 2!'); return; } - const res = await postRequest('/admin/ranking-batch-size', { - ranking_batch_size: r, + const res = await postRequest('/admin/batch-ranking-size', { + batch_ranking_size: r, }); if (res.status !== 200 || res.data?.yes_no !== 1) { errorAlert(res); return; } - alert('Ranking Batch Size updated!'); + alert('Batch Ranking Size updated!'); getOptions(); } @@ -294,7 +294,7 @@ const AdminSettings = () => { Update Categories - Set Ranking Batch Size + Set Batch Ranking Size (BRS) Set how many projects judges rank at a time (must be at least 2 obviously). Judges can rank and reorder projects freely before submitting a batch of the specified size. @@ -305,17 +305,17 @@ const AdminSettings = () => { type="number" min="2" placeholder="8" - value={rankingBatchSize} + value={batchRankingSize} onChange={(e) => { - setRankingBatchSize(e.target.value.toString()); + setBatchRankingSize(e.target.value.toString()); }} />
Judging Parameters
diff --git a/client/src/pages/judge/index.tsx b/client/src/pages/judge/index.tsx index 2d827c9..4468fff 100644 --- a/client/src/pages/judge/index.tsx +++ b/client/src/pages/judge/index.tsx @@ -30,7 +30,7 @@ const Judge = () => { const [ranked, setRanked] = useState([]); const [unranked, setUnranked] = useState([]); const [allRanked, setAllRanked] = useState(false) - const [rankingBatchSize, setRankingBatchSize] = useState(0); + const [batchRankingSize, setBatchRankingSize] = useState(0); const [judgingIsOver, setJudgingIsOver] = useState(false); const [nextButtonDisabled, setNextButtonDisabled] = useState(false); const [nextButtonHelperText, setNextButtonHelperText] = useState(''); @@ -97,13 +97,13 @@ const Judge = () => { } setProjCount(projCountRes.data?.count as number); - // Get Ranking Batch Size - const rankingBatchSizeRes = await getRequest('/rbs'); - if (rankingBatchSizeRes.status !== 200) { - errorAlert(rankingBatchSizeRes); + // Get Batch Ranking Size + const batchRankingSizeRes = await getRequest('/brs'); + if (batchRankingSizeRes.status !== 200) { + errorAlert(batchRankingSizeRes); return; } - setRankingBatchSize(rankingBatchSizeRes.data?.rbs as number); + setBatchRankingSize(batchRankingSizeRes.data?.brs as number); const judgingEndedRes = await getRequest('/check-judging-over') if (judgingEndedRes.status !== 200) { @@ -143,12 +143,12 @@ const Judge = () => { setLoaded(true); }, [judge]); - // Trigger button state ranking batch logic updates when `rankingBatchSize` is set (>0) and/or whenever `ranked` or `unranked` states chance + // Trigger button state ranking batch logic updates when `batchRankingSize` is set (>0) and/or whenever `ranked` or `unranked` states chance useEffect(() => { - if (rankingBatchSize > 0) { - setAllRanked((ranked.length === rankingBatchSize || judgingIsOver) && unranked.length === 0); + if (batchRankingSize > 0) { + setAllRanked((ranked.length === batchRankingSize || judgingIsOver) && unranked.length === 0); - if (ranked.length + unranked.length === rankingBatchSize) { + if (ranked.length + unranked.length === batchRankingSize) { setNextButtonHelperText('Rank and submit your current batch to move on'); setNextButtonDisabled(true); } else { @@ -156,7 +156,7 @@ const Judge = () => { if (!judgingIsOver) setNextButtonDisabled(false); } } - }, [rankingBatchSize, judgingIsOver, ranked, unranked, loaded]); + }, [batchRankingSize, judgingIsOver, ranked, unranked, loaded]); if (!loaded) return ; @@ -277,8 +277,8 @@ const Judge = () => { }; const submitBatch = async () => { - if (ranked.length !== rankingBatchSize && !judgingIsOver) { - alert(`You can only submit rankings in batches of ${rankingBatchSize} projects.`) + if (ranked.length !== batchRankingSize && !judgingIsOver) { + alert(`You can only submit rankings in batches of ${batchRankingSize} projects.`) return } if (ranked.length === 0) { @@ -363,7 +363,7 @@ const Judge = () => {
{/* lucatodo: text updates if judging is ended manually to allow 'early' submission (see issue #4) */} Please rank all your projects to submit.
- +