Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into fgc-2024-fcs
Browse files Browse the repository at this point in the history
  • Loading branch information
jfabellera committed Sep 16, 2024
2 parents b00a3b4 + 067cb2e commit bf0d21e
Show file tree
Hide file tree
Showing 27 changed files with 1,223 additions and 368 deletions.
39 changes: 31 additions & 8 deletions back-end/api/sql/seasons/fgc_2024.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,42 @@ ALTER TABLE "ranking" ADD COLUMN highestScore INT;
ALTER TABLE "ranking" ADD COLUMN foodSecuredPoints INT;

ALTER TABLE "match_detail" ADD COLUMN redResevoirConserved;
ALTER TABLE "match_detail" ADD COLUMN redNexusConserved;
ALTER TABLE "match_detail" ADD COLUMN redFoodProduced;
ALTER TABLE "match_detail" ADD COLUMN redFoodSecured;
ALTER TABLE "match_detail" ADD COLUMN redRobotOneBalanced;
ALTER TABLE "match_detail" ADD COLUMN redRobotTwoBalanced;
ALTER TABLE "match_detail" ADD COLUMN redRobotThreeBalanced;
ALTER TABLE "match_detail" ADD COLUMN redRobotOneParked;
ALTER TABLE "match_detail" ADD COLUMN redRobotTwoParked;
ALTER TABLE "match_detail" ADD COLUMN redRobotThreeParked;
ALTER TABLE "match_detail" ADD COLUMN redCw1 INT;
ALTER TABLE "match_detail" ADD COLUMN redCw2 INT;
ALTER TABLE "match_detail" ADD COLUMN redCw3 INT;
ALTER TABLE "match_detail" ADD COLUMN redCw4 INT;
ALTER TABLE "match_detail" ADD COLUMN redCw5 INT;
ALTER TABLE "match_detail" ADD COLUMN redCw6 INT;
ALTER TABLE "match_detail" ADD COLUMN redEc1 INT;
ALTER TABLE "match_detail" ADD COLUMN redEc2 INT;
ALTER TABLE "match_detail" ADD COLUMN redEc3 INT;
ALTER TABLE "match_detail" ADD COLUMN redEc4 INT;
ALTER TABLE "match_detail" ADD COLUMN redEc5 INT;
ALTER TABLE "match_detail" ADD COLUMN redEc6 INT;

ALTER TABLE "match_detail" ADD COLUMN blueResevoirConserved;
ALTER TABLE "match_detail" ADD COLUMN blueNexusConserved;
ALTER TABLE "match_detail" ADD COLUMN blueFoodProduced;
ALTER TABLE "match_detail" ADD COLUMN blueFoodSecured;
ALTER TABLE "match_detail" ADD COLUMN blueRobotOneBalanced;
ALTER TABLE "match_detail" ADD COLUMN blueRobotTwoBalanced;
ALTER TABLE "match_detail" ADD COLUMN blueRobotThreeBalanced;
ALTER TABLE "match_detail" ADD COLUMN blueRobotOneParked;
ALTER TABLE "match_detail" ADD COLUMN blueRobotTwoParked;
ALTER TABLE "match_detail" ADD COLUMN blueRobotThreeParked;
ALTER TABLE "match_detail" ADD COLUMN blueCw1 INT;
ALTER TABLE "match_detail" ADD COLUMN blueCw2 INT;
ALTER TABLE "match_detail" ADD COLUMN blueCw3 INT;
ALTER TABLE "match_detail" ADD COLUMN blueCw4 INT;
ALTER TABLE "match_detail" ADD COLUMN blueCw5 INT;
ALTER TABLE "match_detail" ADD COLUMN blueCw6 INT;
ALTER TABLE "match_detail" ADD COLUMN blueEc1 INT;
ALTER TABLE "match_detail" ADD COLUMN blueEc2 INT;
ALTER TABLE "match_detail" ADD COLUMN blueEc3 INT;
ALTER TABLE "match_detail" ADD COLUMN blueEc4 INT;
ALTER TABLE "match_detail" ADD COLUMN blueEc5 INT;
ALTER TABLE "match_detail" ADD COLUMN blueEc6 INT;

ALTER TABLE "match_detail" ADD COLUMN coopertition;
ALTER TABLE "match_detail" ADD COLUMN fieldBalanced;
30 changes: 26 additions & 4 deletions back-end/api/src/controllers/Match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {
matchMakerParamsZod,
matchZod,
matchParticipantZod,
reconcileMatchParticipants
reconcileMatchParticipants,
getFunctionsBySeasonKey
} from '@toa-lib/models';
import { NextFunction, Response, Request, Router } from 'express';
import { validateBodyZ } from '../middleware/BodyValidator.js';
import { DataNotFoundError } from '../util/Errors.js';
import { DataNotFoundError, InvalidDataError } from '../util/Errors.js';
import { join } from 'path';
import { writeFile } from 'fs/promises';
import {
Expand Down Expand Up @@ -138,6 +139,13 @@ router.get(
`eventKey = "${eventKey}" AND tournamentKey = "${tournamentKey}" AND id = ${id}`
);

const funcs = getFunctionsBySeasonKey(
eventKey.split('-')[0].toLowerCase()
);
const parsedDetails = funcs?.detailsFromJson
? funcs.detailsFromJson(details)
: details;

for (let i = 0; i < participants.length; i++) {
const [team] = await db.selectAllWhere(
'team',
Expand All @@ -147,7 +155,7 @@ router.get(
}

match.participants = participants;
match.details = details;
match.details = parsedDetails;

res.send(match);
} catch (e) {
Expand Down Expand Up @@ -233,9 +241,23 @@ router.patch(
try {
const { eventKey, tournamentKey, id } = req.params;
const db = await getDB(eventKey);
const funcs = getFunctionsBySeasonKey(
eventKey.split('-')[0].toLowerCase()
);
// Ensure they are actually updating what they say they're updating
if (
req.body.eventKey !== eventKey ||
req.body.tournamentKey !== tournamentKey ||
String(req.body.id) !== id
) {
return next(InvalidDataError);
}
const data = funcs?.detailsToJson
? funcs.detailsToJson(req.body)
: req.body;
await db.updateWhere(
'match_detail',
req.body,
data,
`eventKey = "${eventKey}" AND tournamentKey = "${tournamentKey}" AND id = ${id}`
);
res.status(200).send({});
Expand Down
5 changes: 5 additions & 0 deletions back-end/api/src/util/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export const DataNotFoundError: ApiError = {
message: 'Data requested was not found'
};

export const InvalidDataError: ApiError = {
code: 400,
message: 'The body sent does not match the route parameters. Please ensure the body matches the route.'
};

export const RouteNotFound: ApiError = {
code: 404,
message: 'Route not found.'
Expand Down
4 changes: 3 additions & 1 deletion back-end/realtime/src/rooms/Match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export default class Match extends Room {
// These are in case of mid-match disconnect/reconnects
if (
this.state >= MatchState.PRESTART_COMPLETE &&
this.state !== MatchState.MATCH_COMPLETE &&
this.state !== MatchState.MATCH_COMPLETE && // we never actually get into this state (in the socket server, anyway)
this.state !== MatchState.RESULTS_COMMITTED && // so instead we'll stop prestarting people here instead
this.key &&
!this.timer.inProgress()
) {
Expand Down Expand Up @@ -81,6 +82,7 @@ export default class Match extends Room {
this.emitToAll(MatchSocketEvent.PRESTART, key);
this.emitToAll(MatchSocketEvent.DISPLAY, 1);
this.displayID = 1;
this.state = MatchState.PRESTART_COMPLETE;
logger.info(`prestarting ${key.eventKey}-${key.tournamentKey}-${key.id}`);
});
socket.on(MatchSocketEvent.ABORT, () => {
Expand Down
5 changes: 5 additions & 0 deletions front-end/src/apps/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from 'src/stores/recoil';
import { Tournament } from '@toa-lib/models';
import FrcFmsSettingsTab from './tabs/frc-fms';
import { ViewReturn } from 'src/components/buttons/view-return';
// import FrcFmsSettingsTab from './tabs/frc-fms';

export const SettingsApp: FC = () => {
Expand Down Expand Up @@ -50,6 +51,7 @@ export const SettingsApp: FC = () => {
}
>
<Paper sx={{ marginBottom: (theme) => theme.spacing(8) }}>
<ViewReturn title='Home' href={`/${eventKey}`} sx={{ m: 1 }} />
{/* Tabs */}
<TabContext value={tab}>
<TabList onChange={(e, t) => setTab(t)}>
Expand All @@ -73,6 +75,9 @@ export const SettingsApp: FC = () => {
<FrcFmsSettingsTab />
</TabPanel>
</TabContext>
<Typography variant='caption' sx={{ m: 1 }}>
** Settings Save Automatically
</Typography>
</Paper>
</PaperLayout>
);
Expand Down
2 changes: 1 addition & 1 deletion front-end/src/apps/jb-app/jb-app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC, useState, useEffect } from 'react';
import { DateTime } from 'luxon';
import './JBApp.less';
import './jb-app.less';
import { ChromaLayout } from 'src/layouts/chroma-layout';
import { useRecoilValue } from 'recoil';
import { MatchTimer } from 'src/components/util/match-timer';
Expand Down
7 changes: 6 additions & 1 deletion front-end/src/apps/referee/head-referee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ export const HeadReferee: FC = () => {
<SyncMatchStateToRecoil />
<SyncMatchesToRecoil />
<SyncMatchOccurringToRecoil />
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Box
sx={{ display: 'flex', flexDirection: 'column', gap: '16px', mb: 1 }}
>
<Box sx={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
<seasonComponents.RefereeScoreSheet alliance='red' />
<seasonComponents.RefereeScoreSheet alliance='blue' />
</Box>
</Box>
{seasonComponents.HeadRefExtrasSheet && (
<seasonComponents.HeadRefExtrasSheet />
)}
</RefereeLayout>
);
};
76 changes: 68 additions & 8 deletions front-end/src/apps/scorekeeper/hooks/use-commit-scores.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
import { useRecoilCallback } from 'recoil';
import {
useRecoilCallback,
useRecoilState,
useRecoilValue,
useSetRecoilState
} from 'recoil';
import { useMatchControl } from './use-match-control';
import { MatchState } from '@toa-lib/models';
import { matchOccurringAtom, socketConnectedAtom } from 'src/stores/recoil';
import { patchWholeMatch } from 'src/api/use-match-data';
import {
MatchState,
RESULT_BLUE_WIN,
RESULT_NOT_PLAYED,
RESULT_RED_WIN,
RESULT_TIE
} from '@toa-lib/models';
import {
currentEventKeyAtom,
currentTournamentKeyAtom,
matchesByEventKeyAtomFam,
matchOccurringAtom,
socketConnectedAtom
} from 'src/stores/recoil';
import {
patchWholeMatch,
useMatchesForTournament
} from 'src/api/use-match-data';
import { recalculateRankings } from 'src/api/use-ranking-data';
import { sendAllClear, sendCommitScores } from 'src/api/use-socket';
import { useSeasonFieldControl } from 'src/hooks/use-season-components';

export const useCommitScoresCallback = () => {
const { canCommitScores, setState } = useMatchControl();
const fieldControl = useSeasonFieldControl();
const eventKey = useRecoilValue(currentEventKeyAtom);
const tournamentKey = useRecoilValue(currentTournamentKeyAtom);
const { data: matches, mutate: setMatches } = useMatchesForTournament(
eventKey,
tournamentKey
);

return useRecoilCallback(
({ snapshot }) =>
({ snapshot, set }) =>
async () => {
const match = await snapshot.getPromise(matchOccurringAtom);
const socketConnected = await snapshot.getPromise(socketConnectedAtom);
Expand All @@ -24,15 +51,48 @@ export const useCommitScoresCallback = () => {
if (!match) {
throw new Error('Attempted to commit scores when there is no match.');
}
const { eventKey, tournamentKey, id } = match;
await patchWholeMatch(match);
const pending = { ...match, details: { ...match.details } };
// Update the result if it hasn't been set yet
if (pending.result < 0) {
pending.result =
pending.redScore > pending.blueScore
? RESULT_RED_WIN
: pending.redScore < pending.blueScore
? RESULT_BLUE_WIN
: pending.redScore === pending.blueScore
? RESULT_TIE
: RESULT_NOT_PLAYED;
}

// Extract the important keys
const { eventKey, tournamentKey, id } = pending;

// Update the metadata in the match detail request
if (pending.details && pending.details.id < 0) {
pending.details.id = id;
pending.details.eventKey = eventKey;
pending.details.tournamentKey = tournamentKey;
}

await patchWholeMatch(pending);
// TODO - When to calculate rankings vs. playoff rankings?
await recalculateRankings(eventKey, tournamentKey);
fieldControl?.commitScoresForField?.();
sendCommitScores({ eventKey, tournamentKey, id });
setState(MatchState.RESULTS_COMMITTED);

// If there is no match list, we can't update the matches
if (!matches) return;

// Lastly, update the matches array
const index = matches.findIndex((m) => m.id === id);
if (index >= 0) {
const copy = [...matches];
copy[index] = pending;
setMatches(copy);
}
},
[canCommitScores, setState]
[canCommitScores, setState, matches, eventKey, tournamentKey]
);
};

Expand Down
44 changes: 44 additions & 0 deletions front-end/src/apps/scorekeeper/hooks/use-next-unplayed-match.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useRecoilCallback, useRecoilValue } from 'recoil';
import {
currentEventKeyAtom,
currentMatchIdAtom,
currentTournamentKeyAtom,
matchOccurringAtom
} from 'src/stores/recoil';
import { useMatchesForTournament } from 'src/api/use-match-data';
import { useActiveFieldNumbers } from 'src/components/sync-effects/sync-fields-to-recoil';

export const useNextUnplayedMatch = () => {
const eventKey = useRecoilValue(currentEventKeyAtom);
const tournamentKey = useRecoilValue(currentTournamentKeyAtom);
const { data: matches } = useMatchesForTournament(eventKey, tournamentKey);
const [activeFields] = useActiveFieldNumbers();

return useRecoilCallback(
({ snapshot }) =>
async () => {
const match = await snapshot.getPromise(matchOccurringAtom);

if (!match) {
return null;
}

// Try to find the next match and select it
if (matches) {
const ourMatches = matches
.filter((m) => activeFields.includes(m.fieldNumber))
.sort((a, b) => a.id - b.id);

// Find the next match that hasn't had results posted
const index = ourMatches.findIndex(
(m) => m.id >= match.id && m.result < 0
);
if (ourMatches[index]) {
return ourMatches[index];
}
}
return null;
},
[matches, eventKey, tournamentKey, activeFields]
);
};
Loading

0 comments on commit bf0d21e

Please sign in to comment.