Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better picklists #399

Merged
merged 13 commits into from
Jan 7, 2025
479 changes: 293 additions & 186 deletions components/stats/Picklist.tsx

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,11 +559,12 @@ export interface EventData {
oprRanking: TheBlueAlliance.OprRanking;
}

export type DbPicklist = {
export type CompPicklistGroup = {
_id: string;
picklists: {
[name: string]: number[];
};
strikethroughs: number[];
};

/**
Expand Down
4 changes: 2 additions & 2 deletions lib/api/AccessLevels.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextApiRequest } from "next";
import {
Competition,
DbPicklist,
CompPicklistGroup,
Match,
Pitreport,
Report,
Expand Down Expand Up @@ -404,7 +404,7 @@ namespace AccessLevels {

const picklist = await (
await db
).findObjectById<DbPicklist>(
).findObjectById<CompPicklistGroup>(
CollectionId.Picklists,
new ObjectId(picklistId),
);
Expand Down
9 changes: 6 additions & 3 deletions lib/api/ApiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Team,
Report,
Competition,
DbPicklist,
CompPicklistGroup,
Match,
Pitreport,
Season,
Expand Down Expand Up @@ -67,7 +67,10 @@ export function getCompFromSubjectiveReport(
});
}

export function getCompFromPicklist(db: DbInterface, picklist: DbPicklist) {
export function getCompFromPicklist(
db: DbInterface,
picklist: CompPicklistGroup,
) {
return db.findObject<Competition>(CollectionId.Competitions, {
picklist: picklist._id?.toString(),
});
Expand Down Expand Up @@ -119,7 +122,7 @@ export async function getTeamFromPitReport(db: DbInterface, report: Pitreport) {

export async function getTeamFromPicklist(
db: DbInterface,
picklist: DbPicklist,
picklist: CompPicklistGroup,
) {
return getTeamFromDocument(db, getCompFromPicklist, picklist);
}
Expand Down
17 changes: 9 additions & 8 deletions lib/api/ClientApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Alliance,
Competition,
CompetitonNameIdPair,
DbPicklist,
CompPicklistGroup,
League,
Match,
MatchType,
Expand Down Expand Up @@ -407,6 +407,7 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {

const picklist = await db.addObject(CollectionId.Picklists, {
picklists: {},
strikethroughs: [],
});

const comp = await db.addObject(
Expand Down Expand Up @@ -1237,7 +1238,7 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {

getPicklistFromComp = createNextRoute<
[string],
DbPicklist | undefined,
CompPicklistGroup | undefined,
ApiDependencies,
{ comp: Competition }
>({
Expand All @@ -1246,7 +1247,7 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
handler: async (req, res, { db: dbPromise }, { comp }, [compId]) => {
const db = await dbPromise;

const picklist = await db.findObjectById<DbPicklist>(
const picklist = await db.findObjectById<CompPicklistGroup>(
CollectionId.Picklists,
new ObjectId(comp.picklist),
);
Expand All @@ -1255,11 +1256,11 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
},
});

getPicklist = createNextRoute<
getPicklistGroup = createNextRoute<
[string],
DbPicklist | undefined,
CompPicklistGroup | undefined,
ApiDependencies,
{ picklist: DbPicklist }
{ picklist: CompPicklistGroup }
>({
isAuthorized: (req, res, deps, [picklistId]) =>
AccessLevels.IfOnTeamThatOwnsPicklist(req, res, deps, picklistId),
Expand All @@ -1269,10 +1270,10 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
});

updatePicklist = createNextRoute<
[DbPicklist],
[CompPicklistGroup],
{ result: string },
ApiDependencies,
{ picklist: DbPicklist }
{ picklist: CompPicklistGroup }
>({
isAuthorized: (req, res, deps, [picklist]) =>
AccessLevels.IfOnTeamThatOwnsPicklist(req, res, deps, picklist._id),
Expand Down
4 changes: 2 additions & 2 deletions lib/client/CollectionId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Account,
Session,
Pitreport,
DbPicklist,
CompPicklistGroup,
WebhookHolder,
} from "../Types";

Expand Down Expand Up @@ -54,7 +54,7 @@ export type CollectionIdToType<Id extends CollectionId> =
: Id extends CollectionId.PitReports
? Pitreport
: Id extends CollectionId.Picklists
? DbPicklist
? CompPicklistGroup
: Id extends CollectionId.SubjectiveReports
? SubjectiveReport
: Id extends CollectionId.Webhooks
Expand Down
193 changes: 193 additions & 0 deletions lib/client/PicklistUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// @tested_by tests/lib/client/Picklist.test.ts

import { ObjectId } from "bson";
import { CompPicklistGroup } from "../Types";
import ClientApi from "../api/ClientApi";

export type Picklist = {
index: number;
name: string;
head: PicklistEntry | undefined;
update: (picklist: Picklist) => void;
delete: () => void;
};

export type PicklistEntry = {
number: number;
next?: PicklistEntry;
picklist?: Picklist;
id?: string;
};

export function getPicklistLength(picklist: Picklist) {
let length = 0;
let curr = picklist.head;
while (curr) {
length++;
curr = curr.next;
}

return length;
}

export function removeEntryFromItsPicklist(entry: PicklistEntry) {
if (entry.picklist) {
const picklist = entry.picklist;
if (picklist.head?.id && picklist.head?.id === entry.id) {
picklist.head = picklist.head.next;
} else {
let curr: PicklistEntry | undefined = picklist.head;
while (curr) {
if (curr.next?.number === entry.number) {
curr.next = curr.next.next;
break;
}

curr = curr.next;
}
}

entry.picklist = undefined;

picklist.update(picklist);

return picklist;
}
}

export function insertAfterEntry(entry: PicklistEntry, dragged: PicklistEntry) {
// If you're moving a card into the same spot, don't do anything
if (
entry.number === dragged.number &&
entry.next === dragged.next &&
entry.picklist === dragged.picklist
)
return;

removeEntryFromItsPicklist(dragged);

// Create a copy, don't operate on the original
dragged = {
id: dragged.id,
number: dragged.number,
next: entry.next,
picklist: entry.picklist,
};

entry.next = dragged;

entry.picklist?.update(entry.picklist);

return dragged;
}

export function setHeadOfPicklist(picklist: Picklist, entry: PicklistEntry) {
removeEntryFromItsPicklist(entry);

const newEntry = {
number: entry.number,
next: picklist.head,
picklist,
id: entry.id,
};
picklist.head = newEntry;

picklist.update(picklist);

return newEntry;
}

export function setTailOfPicklist(picklist: Picklist, entry: PicklistEntry) {
removeEntryFromItsPicklist(entry);
entry = {
...entry,
picklist,
next: undefined,
};

let tail = picklist.head;
if (!tail) {
picklist.head = entry;
} else {
while (tail.next) {
tail = tail.next;
}

tail.next = entry;
}

picklist.update(picklist);
return entry;
}

export function savePicklistGroup(
id: string,
picklists: Picklist[],
strikethroughs: number[],
api: ClientApi,
) {
const picklistDict = picklists.reduce<CompPicklistGroup>(
(acc, picklist) => {
const picklistArray: number[] = [];
for (let curr = picklist.head; curr; curr = curr.next) {
picklistArray.push(curr.number);
}

acc.picklists[picklist.name] = picklistArray;
return acc;
},
{
_id: id,
picklists: {},
strikethroughs,
},
);

api.updatePicklist(picklistDict);
}

export function loadPicklistGroup(
picklistDict: CompPicklistGroup,
setStrikethroughs: (strikethroughs: number[]) => void,
setPicklists: (picklists: Picklist[]) => void,
updatePicklist: (picklist: Picklist) => void,
deletePicklist: (picklist: Picklist) => void,
) {
{
setStrikethroughs(picklistDict.strikethroughs);

setPicklists(
Object.entries(picklistDict.picklists).map(([name, teams], index) => {
const newPicklist: Picklist = {
index,
name,
head: undefined,
update: updatePicklist,
delete: () => deletePicklist(newPicklist),
};

if (teams.length > 0) {
let curr: PicklistEntry = {
id: new ObjectId().toString(),
number: teams[0],
next: undefined,
picklist: newPicklist,
};
newPicklist.head = curr;

for (let i = 1; i < teams.length; i++) {
curr.next = {
number: teams[i],
next: undefined,
picklist: newPicklist,
id: new ObjectId().toString(),
};
curr = curr.next;
}
}

return newPicklist;
}),
);
}
}
2 changes: 1 addition & 1 deletion pages/[teamSlug]/[seasonSlug]/[competitonSlug]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
User,
Team,
Competition,
DbPicklist,
CompPicklistGroup,
Season,
} from "@/lib/Types";
import { useCurrentSession } from "@/lib/client/useCurrentSession";
Expand Down
12 changes: 3 additions & 9 deletions pages/[teamSlug]/[seasonSlug]/[competitonSlug]/stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Pitreport,
SubjectiveReport,
Report,
DbPicklist,
CompPicklistGroup,
} from "@/lib/Types";
import { useState, useEffect, useCallback } from "react";
import Container from "@/components/Container";
Expand All @@ -30,7 +30,7 @@ export default function Stats(props: {
pitReports: Pitreport[];
subjectiveReports: SubjectiveReport[];
competition: Competition;
picklists: DbPicklist;
picklists: CompPicklistGroup;
}) {
const [update, setUpdate] = useState(Date.now());
const [updating, setUpdating] = useState(false);
Expand Down Expand Up @@ -86,11 +86,6 @@ export default function Stats(props: {
Object.keys(r.robotComments).forEach((c) => teams.add(+c)),
); //+str converts to number

console.log("Reports", reports);
console.log("PitReports", pitReports);
console.log("SubjectiveReports", subjectiveReports);
console.log("Teams", teams);

return (
<Container
requireAuthentication={false}
Expand Down Expand Up @@ -209,11 +204,10 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
},
);

const picklists = await db.findObjectById<DbPicklist>(
const picklists = await db.findObjectById<CompPicklistGroup>(
CollectionId.Picklists,
new ObjectId(resolved.competition?.picklist),
);
console.log("Found picklists:", resolved.competition?.picklist, picklists);

return {
props: {
Expand Down
Loading
Loading