Skip to content

Commit 9c459db

Browse files
authored
Merge pull request #2895 from codecrafters-io/andy/feat
feat(leaderboard): Implement team selection persistence and add leaderboard team storage service
2 parents 3fd0c5c + 70efda2 commit 9c459db

File tree

3 files changed

+97
-2
lines changed

3 files changed

+97
-2
lines changed

app/components/course-leaderboard/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { tracked } from '@glimmer/tracking';
1111
import type ActionCableConsumerService from 'codecrafters-frontend/services/action-cable-consumer';
1212
import type AnalyticsEventTrackerService from 'codecrafters-frontend/services/analytics-event-tracker';
1313
import type AuthenticatorService from 'codecrafters-frontend/services/authenticator';
14+
import type LeaderboardTeamStorageService from 'codecrafters-frontend/services/leaderboard-team-storage';
1415
import type RepositoryModel from 'codecrafters-frontend/models/repository';
1516
import type RouterService from '@ember/routing/router-service';
1617
import type Store from '@ember-data/store';
@@ -43,13 +44,21 @@ export default class CourseLeaderboardComponent extends Component<Signature> {
4344
@service declare actionCableConsumer: ActionCableConsumerService;
4445
@service declare analyticsEventTracker: AnalyticsEventTrackerService;
4546
@service declare authenticator: AuthenticatorService;
47+
@service declare leaderboardTeamStorage: LeaderboardTeamStorageService;
4648
@service declare router: RouterService;
4749
@service declare store: Store;
4850
@service declare visibility: VisibilityService;
4951

5052
constructor(owner: unknown, args: Signature['Args']) {
5153
super(owner, args);
52-
this.team = this.currentUserTeams[0] ?? null;
54+
55+
if (this.leaderboardTeamStorage.lastSelectionWasGlobalLeaderboard) {
56+
this.team = null;
57+
} else {
58+
const defaultTeam = this.currentUserTeams[0] ?? null;
59+
const lastSelectedTeamId = this.leaderboardTeamStorage.lastSelectedTeamId;
60+
this.team = this.currentUserTeams.find((team) => team.id === lastSelectedTeamId) ?? defaultTeam;
61+
}
5362
}
5463

5564
get currentUserIsTeamMember() {
@@ -189,6 +198,8 @@ export default class CourseLeaderboardComponent extends Component<Signature> {
189198
this.stopLeaderboardPoller();
190199

191200
this.team = team;
201+
this.leaderboardTeamStorage.setlastSelectedTeamId(team?.id ?? null);
202+
192203
// this.entriesFromAPI = [];
193204
this.isReloadingEntries = true;
194205

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Service, { inject as service } from '@ember/service';
2+
import LocalStorageService from 'codecrafters-frontend/services/local-storage';
3+
4+
const GLOBAL_LEADERBOARD_ID = 'GLOBAL_LEADERBOARD_ID';
5+
6+
export default class LeaderboardTeamStorageService extends Service {
7+
static STORAGE_KEY = 'leaderboard-team-selection-v1';
8+
9+
@service declare localStorage: LocalStorageService;
10+
11+
get lastSelectedTeamId(): string | null {
12+
return this.localStorage.getItem(LeaderboardTeamStorageService.STORAGE_KEY);
13+
}
14+
15+
get lastSelectionWasGlobalLeaderboard(): boolean {
16+
return this.lastSelectedTeamId === GLOBAL_LEADERBOARD_ID;
17+
}
18+
19+
setlastSelectedTeamId(teamId: string | null): void {
20+
this.localStorage.setItem(LeaderboardTeamStorageService.STORAGE_KEY, teamId || GLOBAL_LEADERBOARD_ID);
21+
}
22+
}

tests/acceptance/course-page/view-leaderboard-test.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import testScenario from 'codecrafters-frontend/mirage/scenarios/test';
77
import { assertTooltipContent, assertTooltipNotRendered } from 'ember-tooltips/test-support';
88
import { currentURL, settled } from '@ember/test-helpers';
99
import { module, test } from 'qunit';
10-
import { setupAnimationTest } from 'ember-animated/test-support';
10+
import { setupAnimationTest, animationsSettled } from 'ember-animated/test-support';
1111
import { setupApplicationTest } from 'codecrafters-frontend/tests/helpers';
1212
import { signIn, signInAsSubscriber, signInAsTeamMember } from 'codecrafters-frontend/tests/support/authentication-helpers';
1313

@@ -286,6 +286,68 @@ module('Acceptance | course-page | view-leaderboard', function (hooks) {
286286
await percySnapshot('Leaderboard for teams - Viewing World');
287287
});
288288

289+
test('team dropdown selection persists across page refresh', async function (assert) {
290+
testScenario(this.server);
291+
signInAsTeamMember(this.owner, this.server);
292+
293+
let python = this.server.schema.languages.findBy({ name: 'Python' });
294+
let redis = this.server.schema.courses.findBy({ slug: 'redis' });
295+
296+
let otherUser = this.server.create('user', {
297+
id: 'other-user',
298+
avatarUrl: 'https://github.com/Gufran.png',
299+
createdAt: new Date(),
300+
githubUsername: 'Gufran',
301+
username: 'Gufran',
302+
});
303+
304+
this.server.create('course-leaderboard-entry', {
305+
status: 'idle',
306+
currentCourseStage: redis.stages.models.find((x) => x.position === 2),
307+
language: python,
308+
user: otherUser,
309+
lastAttemptAt: new Date(),
310+
});
311+
312+
await catalogPage.visit();
313+
await catalogPage.clickOnCourse('Build your own Redis');
314+
await courseOverviewPage.clickOnStartCourse();
315+
316+
assert.strictEqual(coursePage.leaderboard.entries.length, 0, 'no leaderboard entries should be present by default');
317+
318+
// Open dropdown and switch to "Everyone" view
319+
await coursePage.leaderboard.teamDropdown.toggle();
320+
await coursePage.leaderboard.teamDropdown.clickOnLink('Everyone');
321+
322+
// Verify we're now seeing world leaderboard
323+
assert.strictEqual(coursePage.leaderboard.entries.length, 1, 'leaderboard entries should be visible if filtering by world');
324+
325+
// Refresh the page
326+
await catalogPage.visit();
327+
await catalogPage.clickOnCourse('Build your own Redis');
328+
await courseOverviewPage.clickOnStartCourse();
329+
330+
// Verify the selection persisted
331+
assert.strictEqual(coursePage.leaderboard.entries.length, 1, 'leaderboard entries should still be visible after refresh');
332+
await coursePage.leaderboard.teamDropdown.toggle();
333+
assert.ok(coursePage.leaderboard.teamDropdown.hasLink('Everyone'), 'should still be on Everyone view');
334+
335+
// Switch to "Dummy Team" view
336+
await coursePage.leaderboard.teamDropdown.clickOnLink('Dummy Team');
337+
await animationsSettled();
338+
assert.strictEqual(coursePage.leaderboard.entries.length, 0, 'leaderboard entries should be empty if filtering by team');
339+
340+
// Refresh the page
341+
await catalogPage.visit();
342+
await catalogPage.clickOnCourse('Build your own Redis');
343+
await courseOverviewPage.clickOnStartCourse();
344+
345+
// Verify the selection persisted
346+
assert.strictEqual(coursePage.leaderboard.entries.length, 0, 'leaderboard entries should still be empty after refresh');
347+
await coursePage.leaderboard.teamDropdown.toggle();
348+
assert.ok(coursePage.leaderboard.teamDropdown.hasLink('Dummy Team'), 'should still be on Dummy Team view');
349+
});
350+
289351
test('private leaderboard feature suggestion is shown to non-team members with a prompt', async function (assert) {
290352
testScenario(this.server);
291353

0 commit comments

Comments
 (0)