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

refactor: move funboxes to a shared package (@miodec) #6063

Draft
wants to merge 88 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
1d59409
fix: balloon message clipping on the result page
Miodec Nov 8, 2024
bd545ba
start package structure
Miodec Nov 13, 2024
09b0bca
replace funbox code in backend with package
Miodec Nov 13, 2024
acb0956
only checking if not none
Miodec Nov 13, 2024
bf72a15
better error messages
Miodec Nov 13, 2024
ed3cd9f
frontend
Miodec Nov 14, 2024
078e131
check for empty string
Miodec Nov 15, 2024
96ce4a6
use function for splitting funbox string
Miodec Nov 15, 2024
1551da0
refactor
Miodec Nov 15, 2024
2cb15e9
refactor
Miodec Nov 15, 2024
de899d6
remove unnecessary update function
Miodec Nov 15, 2024
93ea1d2
correct function type
Miodec Nov 15, 2024
79704b5
fix type
Miodec Nov 15, 2024
08d2742
type
Miodec Nov 15, 2024
1a5abe7
type
Miodec Nov 15, 2024
5be1b49
move hascss to properties
Miodec Nov 15, 2024
e6493d2
optional frontendfunctions
Miodec Nov 15, 2024
2263707
optional
Miodec Nov 15, 2024
344c867
typo
Miodec Nov 15, 2024
7bf119b
optional
Miodec Nov 15, 2024
4c4f7f1
stronger property types
Miodec Nov 15, 2024
a868ff0
rename property
Miodec Nov 16, 2024
5dffa50
Merge branch 'master' into shared-funbox
Miodec Nov 19, 2024
5398546
closer to the original implementation
Miodec Nov 19, 2024
39a28e7
comments
Miodec Nov 19, 2024
927754d
better type
Miodec Nov 19, 2024
0cecd65
refactor
Miodec Nov 19, 2024
33d2de7
refactor
Miodec Nov 19, 2024
445fbe2
removed comments
Miodec Nov 19, 2024
db6ad5b
refactor
Miodec Nov 19, 2024
6c9219d
brackets
Miodec Nov 19, 2024
b34d2ae
refactor
Miodec Nov 19, 2024
d411fa1
refactor
Miodec Nov 19, 2024
878c569
refactors
Miodec Nov 19, 2024
8cb0131
remove comments
Miodec Nov 19, 2024
85597d8
export get all function
Miodec Nov 20, 2024
cbb76ec
move function to util file, rename function
Miodec Nov 20, 2024
dda5679
reduce duplication by exporting function that gets active funbox func…
Miodec Nov 20, 2024
519795a
move metadata to shared package
Miodec Nov 20, 2024
f5a9d6f
circular
Miodec Nov 20, 2024
fcf47d1
move functions to package
Miodec Nov 20, 2024
bf815fc
restructure package
Miodec Nov 20, 2024
4308d55
fix import
Miodec Nov 20, 2024
aad23fd
fix imports
Miodec Nov 20, 2024
4676d11
fix imports
Miodec Nov 20, 2024
b3c2b2a
reexport with better name
Miodec Nov 20, 2024
b17da53
rename
Miodec Nov 20, 2024
4f6e32e
restructure: only export index file, reexport necessary functions
Miodec Nov 20, 2024
b71fa7e
rename
Miodec Nov 20, 2024
7a3c5dc
name fix
Miodec Nov 21, 2024
cb00901
export metadata
Miodec Nov 21, 2024
b7d6dc5
merge funbox metadata and functions into one object in the frontend
Miodec Nov 21, 2024
e10e95c
use get active function
Miodec Nov 21, 2024
cc5d5be
add getactivenames function
Miodec Nov 21, 2024
8eb62da
rename type
Miodec Nov 21, 2024
767dbf1
add helper functions
Miodec Nov 21, 2024
ba1ec0d
export type
Miodec Nov 21, 2024
182c297
getting names from list
Miodec Nov 21, 2024
c52deb6
missing property
Miodec Nov 21, 2024
40661ba
mix missing props
Miodec Nov 21, 2024
9f52206
remove nonexistent frontend function
Miodec Nov 21, 2024
09c916f
remove todo
Miodec Nov 21, 2024
d69cf78
use getactive function
Miodec Nov 21, 2024
fbb423f
await to void
Miodec Nov 21, 2024
7cb3ac3
use get active function
Miodec Nov 21, 2024
0379473
refactor
Miodec Nov 21, 2024
e7791fa
use get active
Miodec Nov 21, 2024
f542604
refactor
Miodec Nov 21, 2024
0092fa7
use getactive function
Miodec Nov 21, 2024
fd5d455
use getactive
Miodec Nov 21, 2024
09bdcff
add getfromstring function
Miodec Nov 21, 2024
09b524b
wrong function
Miodec Nov 25, 2024
95abe8c
more descriptive names
Miodec Nov 25, 2024
ee3f345
remove import
Miodec Nov 25, 2024
f960c5a
remove console log
Miodec Nov 25, 2024
48d5b5a
rewrite to accept funbox metadatas
Miodec Nov 25, 2024
871584e
move function
Miodec Nov 25, 2024
afcd752
move function back
Miodec Nov 25, 2024
f6183a2
import from package to avoid circular
Miodec Nov 26, 2024
af78e7c
accept array of funboxes instead of importing funbox
Miodec Nov 26, 2024
3cf91cb
remove unused import
Miodec Nov 26, 2024
ae8d6bd
remove comment
Miodec Nov 27, 2024
fb3610c
add test to make sure package and frontend function implementation is…
Miodec Nov 27, 2024
4269d59
remove test command for now since no tests are defined
Miodec Nov 27, 2024
7945660
fix mismatch
Miodec Nov 27, 2024
6fe4345
unnecessary function
Miodec Nov 27, 2024
3fd9a42
missing properties
Miodec Nov 27, 2024
ea153f7
refactor test
Miodec Nov 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@date-fns/utc": "1.2.0",
"@monkeytype/contracts": "workspace:*",
"@monkeytype/funbox": "workspace:*",
"@monkeytype/util": "workspace:*",
"@ts-rest/core": "3.51.0",
"@ts-rest/express": "3.51.0",
Expand Down
29 changes: 19 additions & 10 deletions backend/src/api/controllers/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Logger from "../../utils/logger";
import "dotenv/config";
import { MonkeyResponse } from "../../utils/monkey-response";
import MonkeyError from "../../utils/error";
import { areFunboxesCompatible, isTestTooShort } from "../../utils/validation";
import { isTestTooShort } from "../../utils/validation";
import {
implemented as anticheatImplemented,
validateResult,
Expand All @@ -22,7 +22,6 @@ import { getDailyLeaderboard } from "../../utils/daily-leaderboards";
import AutoRoleList from "../../constants/auto-roles";
import * as UserDAL from "../../dal/user";
import { buildMonkeyMail } from "../../utils/monkey-mail";
import FunboxList from "../../constants/funbox-list";
import _, { omit } from "lodash";
import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
import { UAParser } from "ua-parser-js";
Expand Down Expand Up @@ -57,6 +56,11 @@ import {
getStartOfDayTimestamp,
} from "@monkeytype/util/date-and-time";
import { MonkeyRequest } from "../types";
import {
getFunbox,
checkCompatibility,
stringToFunboxNames,
} from "@monkeytype/funbox";

try {
if (!anticheatImplemented()) throw new Error("undefined");
Expand Down Expand Up @@ -232,7 +236,9 @@ export async function addResult(
}
}

if (!areFunboxesCompatible(completedEvent.funbox ?? "")) {
const funboxNames = stringToFunboxNames(completedEvent.funbox ?? "");

if (!checkCompatibility(funboxNames)) {
throw new MonkeyError(400, "Impossible funbox combination");
}

Expand Down Expand Up @@ -660,7 +666,7 @@ async function calculateXp(
charStats,
punctuation,
numbers,
funbox,
funbox: resultFunboxes,
} = result;

const {
Expand Down Expand Up @@ -713,12 +719,15 @@ async function calculateXp(
}
}

if (funboxBonusConfiguration > 0) {
const funboxModifier = _.sumBy(funbox.split("#"), (funboxName) => {
const funbox = FunboxList.find((f) => f.name === funboxName);
const difficultyLevel = funbox?.difficultyLevel ?? 0;
return Math.max(difficultyLevel * funboxBonusConfiguration, 0);
});
if (funboxBonusConfiguration > 0 && resultFunboxes !== "none") {
const funboxModifier = _.sumBy(
stringToFunboxNames(resultFunboxes),
(funboxName) => {
const funbox = getFunbox(funboxName);
const difficultyLevel = funbox?.difficultyLevel ?? 0;
return Math.max(difficultyLevel * funboxBonusConfiguration, 0);
}
);
if (funboxModifier > 0) {
modifier += funboxModifier;
breakdown.funbox = Math.round(baseXp * funboxModifier);
Expand Down
23 changes: 9 additions & 14 deletions backend/src/utils/pb.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import _ from "lodash";
import FunboxList from "../constants/funbox-list";

import {
Mode,
PersonalBest,
PersonalBests,
} from "@monkeytype/contracts/schemas/shared";
import { Result as ResultType } from "@monkeytype/contracts/schemas/results";
import { getFunboxesFromString } from "@monkeytype/funbox";

export type LbPersonalBests = {
time: Record<number, Record<string, PersonalBest>>;
Expand All @@ -21,20 +20,16 @@ type CheckAndUpdatePbResult = {
type Result = Omit<ResultType<Mode>, "_id" | "name">;

export function canFunboxGetPb(result: Result): boolean {
const funbox = result.funbox;
if (funbox === undefined || funbox === "" || funbox === "none") return true;

let ret = true;
const resultFunboxes = funbox.split("#");
for (const funbox of FunboxList) {
if (resultFunboxes.includes(funbox.name)) {
if (!funbox.canGetPb) {
ret = false;
}
}
const funboxString = result.funbox;
if (
funboxString === undefined ||
funboxString === "" ||
funboxString === "none"
) {
return true;
}

return ret;
return getFunboxesFromString(funboxString).every((f) => f.canGetPb);
}

export function checkAndUpdatePb(
Expand Down
137 changes: 0 additions & 137 deletions backend/src/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import _ from "lodash";
import { default as FunboxList } from "../constants/funbox-list";
import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
import { intersect } from "@monkeytype/util/arrays";

export function isTestTooShort(result: CompletedEvent): boolean {
const { mode, mode2, customText, testDuration, bailedOut } = result;
Expand Down Expand Up @@ -48,138 +46,3 @@ export function isTestTooShort(result: CompletedEvent): boolean {

return false;
}

export function areFunboxesCompatible(funboxesString: string): boolean {
const funboxes = funboxesString.split("#").filter((f) => f !== "none");

const funboxesToCheck = FunboxList.filter((f) => funboxes.includes(f.name));

const allFunboxesAreValid = funboxesToCheck.length === funboxes.length;
const oneWordModifierMax =
funboxesToCheck.filter(
(f) =>
f.frontendFunctions?.includes("getWord") ??
f.frontendFunctions?.includes("pullSection") ??
f.frontendFunctions?.includes("withWords")
).length <= 1;
const layoutUsability =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "changesLayout")
).length === 0 ||
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "ignoresLayout" || fp === "usesLayout")
).length === 0;
const oneNospaceOrToPushMax =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "nospace" || fp.startsWith("toPush"))
).length <= 1;
const oneWordOrderMax =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp.startsWith("wordOrder"))
).length <= 1;
const oneChangesWordsVisibilityMax =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "changesWordsVisibility")
).length <= 1;
const oneFrequencyChangesMax =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "changesWordsFrequency")
).length <= 1;
const noFrequencyChangesConflicts =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "changesWordsFrequency")
).length === 0 ||
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "ignoresLanguage")
).length === 0;
const capitalisationChangePosibility =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "noLetters")
).length === 0 ||
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "changesCapitalisation")
).length === 0;
const noConflictsWithSymmetricChars =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "conflictsWithSymmetricChars")
).length === 0 ||
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "symmetricChars")
).length === 0;
const canSpeak =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "speaks" || fp === "unspeakable")
).length <= 1;
const hasLanguageToSpeak =
funboxesToCheck.filter((f) => f.properties?.find((fp) => fp === "speaks"))
.length === 0 ||
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "ignoresLanguage")
).length === 0;
const oneToPushOrPullSectionMax =
funboxesToCheck.filter(
(f) =>
f.properties?.some((fp) => fp.startsWith("toPush:")) ??
f.frontendFunctions?.includes("pullSection")
).length <= 1;
// const oneApplyCSSMax =
// funboxesToCheck.filter((f) => f.frontendFunctions?.includes("applyCSS"))
// .length <= 1; //todo: move all funbox stuff to the shared package, this is ok to remove for now
const onePunctuateWordMax =
funboxesToCheck.filter((f) =>
f.frontendFunctions?.includes("punctuateWord")
).length <= 1;
const oneCharCheckerMax =
funboxesToCheck.filter((f) =>
f.frontendFunctions?.includes("isCharCorrect")
).length <= 1;
const oneCharReplacerMax =
funboxesToCheck.filter((f) => f.frontendFunctions?.includes("getWordHtml"))
.length <= 1;
const oneChangesCapitalisationMax =
funboxesToCheck.filter((f) =>
f.properties?.find((fp) => fp === "changesCapitalisation")
).length <= 1;
const allowedConfig = {} as Record<string, string[] | boolean[]>;
let noConfigConflicts = true;
for (const f of funboxesToCheck) {
if (!f.frontendForcedConfig) continue;
for (const key in f.frontendForcedConfig) {
const allowedConfigValue = allowedConfig[key];
const funboxValue = f.frontendForcedConfig[key];
if (allowedConfigValue !== undefined && funboxValue !== undefined) {
if (
intersect<string | boolean>(allowedConfigValue, funboxValue, true)
.length === 0
) {
noConfigConflicts = false;
break;
}
} else if (funboxValue !== undefined) {
allowedConfig[key] = funboxValue;
}
}
}

return (
allFunboxesAreValid &&
oneWordModifierMax &&
layoutUsability &&
oneNospaceOrToPushMax &&
oneChangesWordsVisibilityMax &&
oneFrequencyChangesMax &&
noFrequencyChangesConflicts &&
capitalisationChangePosibility &&
noConflictsWithSymmetricChars &&
canSpeak &&
hasLanguageToSpeak &&
oneToPushOrPullSectionMax &&
// oneApplyCSSMax &&
onePunctuateWordMax &&
oneCharCheckerMax &&
oneCharReplacerMax &&
oneChangesCapitalisationMax &&
noConfigConflicts &&
oneWordOrderMax
);
}
6 changes: 2 additions & 4 deletions frontend/__tests__/root/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,8 @@ describe("Config", () => {
expect(Config.setFavThemes([stringOfLength(51)])).toBe(false);
});
it("setFunbox", () => {
expect(Config.setFunbox("one")).toBe(true);
expect(Config.setFunbox("one#two")).toBe(true);
expect(Config.setFunbox("one#two#")).toBe(true);
expect(Config.setFunbox(stringOfLength(100))).toBe(true);
expect(Config.setFunbox("mirror")).toBe(true);
expect(Config.setFunbox("mirror#58008")).toBe(true);

expect(Config.setFunbox(stringOfLength(101))).toBe(false);
});
Expand Down
24 changes: 24 additions & 0 deletions frontend/__tests__/test/funbox.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getAllFunboxes } from "../../src/ts/test/funbox/list";

describe("funbox", () => {
describe("list", () => {
it("should have every frontendFunctions function defined", () => {
for (const funbox of getAllFunboxes()) {
const packageFunctions = (funbox.frontendFunctions ?? []).sort();
const implementations = Object.keys(funbox.functions ?? {}).sort();

let message = "has mismatched functions";

if (packageFunctions.length > implementations.length) {
message = `missing function implementation in frontend`;
} else if (implementations.length > packageFunctions.length) {
message = `missing properties in frontendFunctions in the package`;
}

expect(packageFunctions, `Funbox ${funbox.name} ${message}`).toEqual(
implementations
);
}
});
});
});
2 changes: 1 addition & 1 deletion frontend/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
"ts-node": {
"files": true
},
"files": ["../src/ts/types/types.d.ts", "vitest.d.ts"],
"files": ["vitest.d.ts"],
"include": ["./**/*.spec.ts", "./setup-tests.ts"]
}
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"dependencies": {
"@date-fns/utc": "1.2.0",
"@monkeytype/contracts": "workspace:*",
"@monkeytype/funbox": "workspace:*",
"@monkeytype/util": "workspace:*",
"@ts-rest/core": "3.51.0",
"canvas-confetti": "1.5.1",
Expand Down
20 changes: 1 addition & 19 deletions frontend/src/ts/commandline/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import PresetsCommands from "./lists/presets";
import LayoutsCommands, {
update as updateLayoutsCommands,
} from "./lists/layouts";
import FunboxCommands, { update as updateFunboxCommands } from "./lists/funbox";
import FunboxCommands from "./lists/funbox";
import ThemesCommands, { update as updateThemesCommands } from "./lists/themes";
import LoadChallengeCommands, {
update as updateLoadChallengeCommands,
Expand Down Expand Up @@ -131,22 +131,6 @@ languagesPromise
);
});

const funboxPromise = JSONData.getFunboxList();
funboxPromise
.then((funboxes) => {
updateFunboxCommands(funboxes);
if (FunboxCommands[0]?.subgroup) {
FunboxCommands[0].subgroup.beforeList = (): void => {
updateFunboxCommands(funboxes);
};
}
})
.catch((e: unknown) => {
console.error(
Misc.createErrorMessage(e, "Failed to update funbox commands")
);
});

const fontsPromise = JSONData.getFontsList();
fontsPromise
.then((fonts) => {
Expand Down Expand Up @@ -517,7 +501,6 @@ export async function getList(
await Promise.allSettled([
layoutsPromise,
languagesPromise,
funboxPromise,
fontsPromise,
themesPromise,
challengesPromise,
Expand Down Expand Up @@ -565,7 +548,6 @@ export async function getSingleSubgroup(): Promise<CommandsSubgroup> {
await Promise.allSettled([
layoutsPromise,
languagesPromise,
funboxPromise,
fontsPromise,
themesPromise,
challengesPromise,
Expand Down
Loading