From 2b23ed434c97d47121bd0af5755b06648100d442 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 10 Sep 2023 00:33:55 +0300 Subject: [PATCH] Leaderboards support season switching --- .../leaderboards/leaderboards-constants.ts | 2 +- .../seasonPopularUsersWeapon.server.ts | 3 +- .../queries/teamSPLeaderboard.server.ts | 1 + .../queries/userSPLeaderboard.server.ts | 1 + .../leaderboards/routes/leaderboards.tsx | 117 +++++++++++++----- 5 files changed, 91 insertions(+), 33 deletions(-) diff --git a/app/features/leaderboards/leaderboards-constants.ts b/app/features/leaderboards/leaderboards-constants.ts index 8046d9dc4b..c2e763bcc0 100644 --- a/app/features/leaderboards/leaderboards-constants.ts +++ b/app/features/leaderboards/leaderboards-constants.ts @@ -2,7 +2,7 @@ import { mainWeaponIds, weaponCategories } from "~/modules/in-game-lists"; import { rankedModesShort } from "~/modules/in-game-lists/modes"; export const MATCHES_COUNT_NEEDED_FOR_LEADERBOARD = 7; -export const LEADERBOARD_MAX_SIZE = 250; +export const LEADERBOARD_MAX_SIZE = 500; export const LEADERBOARD_TYPES = [ "USER", diff --git a/app/features/leaderboards/queries/seasonPopularUsersWeapon.server.ts b/app/features/leaderboards/queries/seasonPopularUsersWeapon.server.ts index 0fecd27a1d..af32766470 100644 --- a/app/features/leaderboards/queries/seasonPopularUsersWeapon.server.ts +++ b/app/features/leaderboards/queries/seasonPopularUsersWeapon.server.ts @@ -1,6 +1,5 @@ import { sql } from "~/db/sql"; import type { User } from "~/db/types"; -import type { RankingSeason } from "~/features/mmr/season"; import { seasonObject } from "~/features/mmr/season"; import type { MainWeaponId } from "~/modules/in-game-lists"; import { dateToDatabaseTimestamp } from "~/utils/dates"; @@ -28,7 +27,7 @@ const stm = sql.prepare(/* sql */ ` export type SeasonPopularUsersWeapon = Record; export function seasonPopularUsersWeapon( - season: RankingSeason["nth"], + season: number, ): SeasonPopularUsersWeapon { const { starts, ends } = seasonObject(season); diff --git a/app/features/leaderboards/queries/teamSPLeaderboard.server.ts b/app/features/leaderboards/queries/teamSPLeaderboard.server.ts index 9c4a9ebc2f..bcf2947dd6 100644 --- a/app/features/leaderboards/queries/teamSPLeaderboard.server.ts +++ b/app/features/leaderboards/queries/teamSPLeaderboard.server.ts @@ -47,6 +47,7 @@ const stm = sql.prepare(/* sql */ ` inner join ( select "identifier", max("id") as "maxId" from "Skill" + where "season" = @season group by "identifier" ) "Latest" on "Skill"."identifier" = "Latest"."identifier" and "Skill"."id" = "Latest"."maxId" where diff --git a/app/features/leaderboards/queries/userSPLeaderboard.server.ts b/app/features/leaderboards/queries/userSPLeaderboard.server.ts index 1dbc64391c..e633e67f84 100644 --- a/app/features/leaderboards/queries/userSPLeaderboard.server.ts +++ b/app/features/leaderboards/queries/userSPLeaderboard.server.ts @@ -24,6 +24,7 @@ const stm = sql.prepare(/* sql */ ` inner join ( select "userId", max("id") as "maxId" from "Skill" + where "season" = @season group by "userId" ) "Latest" on "Skill"."userId" = "Latest"."userId" and "Skill"."id" = "Latest"."maxId" where diff --git a/app/features/leaderboards/routes/leaderboards.tsx b/app/features/leaderboards/routes/leaderboards.tsx index d69a268030..a70d71d550 100644 --- a/app/features/leaderboards/routes/leaderboards.tsx +++ b/app/features/leaderboards/routes/leaderboards.tsx @@ -41,7 +41,11 @@ import { type RankedModeShort, } from "~/modules/in-game-lists"; import { rankedModesShort } from "~/modules/in-game-lists/modes"; -import { allSeasons } from "~/features/mmr/season"; +import { + allSeasons, + currentSeason, + previousOrCurrentSeason, +} from "~/features/mmr/season"; import { addPlacementRank, addTiers, @@ -85,30 +89,35 @@ export const links: LinksFunction = () => { }; const TYPE_SEARCH_PARAM_KEY = "type"; +const SEASON_SEARCH_PARAM_KEY = "season"; export const loader = async ({ request }: LoaderArgs) => { const t = await i18next.getFixedT(request); const unvalidatedType = new URL(request.url).searchParams.get( TYPE_SEARCH_PARAM_KEY, ); + const unvalidatedSeason = new URL(request.url).searchParams.get( + SEASON_SEARCH_PARAM_KEY, + ); const type = LEADERBOARD_TYPES.find((type) => type === unvalidatedType) ?? LEADERBOARD_TYPES[0]; + const season = + allSeasons(new Date()).find((s) => s === Number(unvalidatedSeason)) ?? + previousOrCurrentSeason(new Date())!.nth; const userLeaderboard = type.includes("USER") ? await cachified({ - // TODO: add season here - key: `user-leaderboard-season-${0}`, + key: `user-leaderboard-season-${season}`, cache, ttl: ttl(HALF_HOUR_IN_MS), // eslint-disable-next-line @typescript-eslint/require-await async getFreshValue() { - const leaderboard = userSPLeaderboard(0); - // TODO: dynamic season - const withTiers = addTiers(leaderboard, 0); + const leaderboard = userSPLeaderboard(season); + const withTiers = addTiers(leaderboard, season); - return addWeapons(withTiers, seasonPopularUsersWeapon(0)); + return addWeapons(withTiers, seasonPopularUsersWeapon(season)); }, }) : null; @@ -116,13 +125,12 @@ export const loader = async ({ request }: LoaderArgs) => { const teamLeaderboard = type === "TEAM" ? await cachified({ - // TODO: add season here - key: `team-leaderboard-season-${0}`, + key: `team-leaderboard-season-${season}`, cache, ttl: ttl(HALF_HOUR_IN_MS), // eslint-disable-next-line @typescript-eslint/require-await async getFreshValue() { - const leaderboard = teamSPLeaderboard(0); + const leaderboard = teamSPLeaderboard(season); const filteredByUser = oneEntryPerUser(leaderboard); return addPlacementRank(filteredByUser); @@ -138,7 +146,6 @@ export const loader = async ({ request }: LoaderArgs) => { ) : userLeaderboard; - // TODO: season selection logic return { userLeaderboard: filteredLeaderboard ?? userLeaderboard, teamLeaderboard, @@ -151,6 +158,7 @@ export const loader = async ({ request }: LoaderArgs) => { ? weaponXPLeaderboard(Number(type.split("-")[2]) as MainWeaponId) : null, title: makeTitle(t("pages.leaderboards")), + season, }; }; @@ -163,14 +171,52 @@ export default function LeaderboardsPage() { !searchParams.get(TYPE_SEARCH_PARAM_KEY) || searchParams.get(TYPE_SEARCH_PARAM_KEY) === "USER"; + const seasonPlusTypeToKey = ({ + season, + type, + }: { + season: number; + type: string; + }) => `${type};${season}`; + + const selectValue = () => { + const type = + searchParams.get(TYPE_SEARCH_PARAM_KEY) ?? LEADERBOARD_TYPES[0]; + + if ( + LEADERBOARD_TYPES.includes(type as (typeof LEADERBOARD_TYPES)[number]) + ) { + return seasonPlusTypeToKey({ + season: data.season, + type, + }); + } + + return type; + }; + + const showTopTen = Boolean( + seasonHasTopTen(data.season) && + isAllUserLeaderboard && + data.userLeaderboard, + ); + + const renderNoEntries = + (data.userLeaderboard && data.userLeaderboard.length === 0) || + (data.teamLeaderboard && data.teamLeaderboard.length === 0); + return (
- {/* TODO: dynamic season */} - {seasonHasTopTen(0) && isAllUserLeaderboard && data.userLeaderboard ? ( + {showTopTen ? (
- {data.userLeaderboard - .filter((_, i) => i <= 9) + {data + .userLeaderboard!.filter((_, i) => i <= 9) .map((entry, i) => { return ( - // TODO dynamic season ); @@ -249,15 +296,22 @@ export default function LeaderboardsPage() { ) : null} {data.teamLeaderboard ? ( ) : null} {data.xpLeaderboard ? : null} - {/* TODO: only when viewing current season */} - {!data.xpLeaderboard ? ( -
+ + {renderNoEntries ? ( +
+ No players on the leaderboard yet +
+ ) : null} + + {!data.xpLeaderboard && data.season === currentSeason(new Date())?.nth ? ( +
Leaderboard is updated once every 30 minutes.
) : null} @@ -268,14 +322,18 @@ export default function LeaderboardsPage() { function PlayersTable({ entries, showTiers, + showingTopTen, }: { entries: NonNullable["userLeaderboard"]>; showTiers?: boolean; + showingTopTen?: boolean; }) { + const data = useLoaderData(); return (
{entries - .filter((_, i) => !seasonHasTopTen(0) || i > 9) + // hide normal rows that are showed in "fancy" top 10 format + .filter((_, i) => !showingTopTen || i > 9) .map((entry) => { return ( @@ -286,9 +344,8 @@ function PlayersTable({ {entry.tier.isPlus ? "+" : ""}
) : null} - {/* TODO: dynamic season */}