From 8ee7e94d465dd39ef7799374589aba743931f253 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Wed, 12 Nov 2025 15:33:32 +0100 Subject: [PATCH] fix(leaderboards): show correct rank in friends weekly leaderboard (@fehmer) (#7104) --- .../api/controllers/leaderboard.spec.ts | 53 ++++++++++++------- backend/src/api/controllers/leaderboard.ts | 11 +++- frontend/src/ts/pages/leaderboards.ts | 10 ++-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/backend/__tests__/api/controllers/leaderboard.spec.ts b/backend/__tests__/api/controllers/leaderboard.spec.ts index 99f0227aa..d06b906f3 100644 --- a/backend/__tests__/api/controllers/leaderboard.spec.ts +++ b/backend/__tests__/api/controllers/leaderboard.spec.ts @@ -1265,10 +1265,13 @@ describe("Loaderboard Controller", () => { describe("get xp weekly leaderboard rank", () => { const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get"); const getRankMock = vi.fn(); + const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids"); beforeEach(async () => { - getXpWeeklyLeaderboardMock.mockClear(); - getRankMock.mockClear(); + [getXpWeeklyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach( + (it) => it.mockClear() + ); + await weeklyLeaderboardEnabled(true); vi.useFakeTimers(); vi.setSystemTime(1722606812000); @@ -1313,25 +1316,13 @@ describe("Loaderboard Controller", () => { expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1); - expect(getRankMock).toHaveBeenCalledWith(uid, lbConf); + expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined); }); it("should get for last week", async () => { //GIVEN const lbConf = (await configuration).leaderboards.weeklyXp; - - const resultData: XpLeaderboardEntry = { - totalXp: 100, - rank: 1, - timeTypedSeconds: 100, - uid: "user1", - name: "user1", - discordId: "discordId", - discordAvatar: "discordAvatar", - lastActivityTimestamp: 1000, - }; - - getRankMock.mockResolvedValue(resultData); + getRankMock.mockResolvedValue({}); //WHEN const { body } = await mockApp @@ -1343,7 +1334,7 @@ describe("Loaderboard Controller", () => { //THEN expect(body).toEqual({ message: "Weekly xp leaderboard rank retrieved", - data: resultData, + data: {}, }); expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith( @@ -1351,7 +1342,33 @@ describe("Loaderboard Controller", () => { 1721606400000 ); - expect(getRankMock).toHaveBeenCalledWith(uid, lbConf); + expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined); + }); + + it("should get for friendsOnly", async () => { + //GIVEN + const lbConf = (await configuration).leaderboards.weeklyXp; + await enableConnectionsFeature(true); + getRankMock.mockResolvedValue({}); + const friends = ["friendOne", "friendTwo"]; + getFriendsUidsMock.mockResolvedValue(friends); + + //WHEN + const { body } = await mockApp + .get("/leaderboards/xp/weekly/rank") + .query({ friendsOnly: true }) + .set("Authorization", `Bearer ${uid}`) + .expect(200); + + //THEN + expect(body).toEqual({ + message: "Weekly xp leaderboard rank retrieved", + data: {}, + }); + + expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1); + + expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, friends); }); it("fails if daily leaderboards are disabled", async () => { diff --git a/backend/src/api/controllers/leaderboard.ts b/backend/src/api/controllers/leaderboard.ts index 175e7b78f..ae7c47a74 100644 --- a/backend/src/api/controllers/leaderboard.ts +++ b/backend/src/api/controllers/leaderboard.ts @@ -248,7 +248,15 @@ export async function getWeeklyXpLeaderboard( export async function getWeeklyXpLeaderboardRank( req: MonkeyRequest ): Promise { + const { friendsOnly } = req.query; const { uid } = req.ctx.decodedToken; + const connectionsConfig = req.ctx.configuration.connections; + + const friendUids = await getFriendsUids( + uid, + friendsOnly === true, + connectionsConfig + ); const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError( req.ctx.configuration.leaderboards.weeklyXp, @@ -256,7 +264,8 @@ export async function getWeeklyXpLeaderboardRank( ); const rankEntry = await weeklyXpLeaderboard.getRank( uid, - req.ctx.configuration.leaderboards.weeklyXp + req.ctx.configuration.leaderboards.weeklyXp, + friendUids ); return new MonkeyResponse("Weekly xp leaderboard rank retrieved", rankEntry); diff --git a/frontend/src/ts/pages/leaderboards.ts b/frontend/src/ts/pages/leaderboards.ts index 977ff152c..2c489faf8 100644 --- a/frontend/src/ts/pages/leaderboards.ts +++ b/frontend/src/ts/pages/leaderboards.ts @@ -766,9 +766,13 @@ function fillUser(): void { } const userData = state.userData; - const percentile = (userData.rank / state.count) * 100; + const rank = state.friendsOnly + ? (userData.friendsRank as number) + : userData.rank; + const percentile = (rank / state.count) * 100; + let percentileString = `Top ${percentile.toFixed(2)}%`; - if (userData.rank === 1) { + if (rank === 1) { percentileString = "GOAT"; } @@ -805,7 +809,7 @@ function fillUser(): void { }; const html = ` -
${formatRank(userData.rank)}
+
${formatRank(rank)}
You (${percentileString})
${diffText}