Skip to content

Commit eddafcd

Browse files
authored
Frontend - Leaderboard latest updates #1076
Frontend - Leaderboard latest updates
2 parents 5c8115e + d1d5071 commit eddafcd

File tree

11 files changed

+85
-20
lines changed

11 files changed

+85
-20
lines changed

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "frontend",
3-
"version": "1.0.129",
3+
"version": "1.0.130",
44
"scripts": {
55
"install:deps": "npm install",
66
"start": "npm install && ng serve --configuration local --open",

frontend/src/app/app.component.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
@if (showLandingPage$ | async) {
2-
<mina-leaderboard-landing-page></mina-leaderboard-landing-page>
3-
<!-- <mina-web-node-landing-page (goToNode)="goToWebNode()"-->
4-
<!-- (stopRequests)="clearNodeUpdateSubscription()"></mina-web-node-landing-page>-->
2+
@if (showLeaderboard) {
3+
<router-outlet></router-outlet>
4+
<!-- <mina-leaderboard-landing-page></mina-leaderboard-landing-page>-->
5+
} @else {
6+
<mina-web-node-landing-page (goToNode)="goToWebNode()"
7+
(stopRequests)="clearNodeUpdateSubscription()"></mina-web-node-landing-page>
8+
}
59
} @else if (showLoadingWebNodePage$ | async) {
610
<router-outlet></router-outlet>
711
} @else if (showLeaderboardPage$ | async) {
@@ -29,7 +33,7 @@
2933
[class.no-toolbar]="hideToolbar"
3034
[class.no-submenus]="subMenusLength < 2"
3135
[class.mobile]="menu.isMobile"
32-
[class.uptime]="showUptime">
36+
[class.uptime]="showLeaderboard">
3337
<router-outlet></router-outlet>
3438
</div>
3539
@if (!isDesktop) {

frontend/src/app/app.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class AppComponent extends StoreDispatcher implements OnInit {
2828
readonly showLeaderboardPage$: Observable<boolean> = this.select$(getMergedRoute).pipe(filter(Boolean), map((route: MergedRoute) => route.url.startsWith(`/${Routes.LEADERBOARD}`)));
2929
subMenusLength: number = 0;
3030
hideToolbar: boolean = CONFIG.hideToolbar;
31-
showUptime: boolean = CONFIG.showLeaderboard;
31+
showLeaderboard: boolean = CONFIG.showLeaderboard;
3232
loaded: boolean;
3333
isDesktop: boolean = isDesktop();
3434

frontend/src/app/app.routing.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,13 @@ function generateRoutes(): Routes {
8585
title: WEBNODE_TITLE,
8686
canActivate: [landingPageGuard],
8787
},
88-
// {
89-
// path: '',
90-
// loadChildren: () => import('@leaderboard/leaderboard.module').then(m => m.LeaderboardModule),
91-
// },
9288
];
9389
if (CONFIG.showLeaderboard) {
9490
routes.push({
9591
path: '',
9692
loadChildren: () => import('@leaderboard/leaderboard.module').then(m => m.LeaderboardModule),
9793
});
98-
}
99-
if (CONFIG.showWebNodeLandingPage) {
94+
} else if (CONFIG.showWebNodeLandingPage) {
10095
routes.push({
10196
path: '',
10297
component: WebNodeLandingPageComponent,

frontend/src/app/core/services/web-node.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class WebNodeService {
142142
return throwError(() => new Error(error.message));
143143
}),
144144
switchMap(() => this.webnode$.asObservable()),
145-
// filter(() => CONFIG.globalConfig.heartbeats),
145+
filter(() => CONFIG.globalConfig.heartbeats),
146146
switchMap(() => timer(0, 60000)),
147147
switchMap(() => this.heartBeat$),
148148
switchMap(heartBeat => this.firestore.addHeartbeat(heartBeat)),

frontend/src/app/features/leaderboard/leaderboard-header/leaderboard-header.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
[@dropdownAnimation]="isMenuOpen ? 'open' : 'closed'"
77
[class.open]="isMenuOpen"
88
(clickOutside)="closeMenu()">
9-
<a [class.active]="route === '/'" routerLink="">Mina Web Node</a>
109
<a [class.active]="route === '/leaderboard'" routerLink="/leaderboard">Leaderboard</a>
11-
<a target="_blank" href="https://docs.google.com/document/d/1Z8A6V2TgY9je1AyeEtA3WTFBYvyBo-eJr-QTydBsXbs/edit?usp=sharing">Round 1 Details</a>
10+
<a target="_blank" href="https://docs.google.com/document/d/1Z8A6V2TgY9je1AyeEtA3WTFBYvyBo-eJr-QTydBsXbs/edit?usp=sharing">Program Details</a>
11+
<a target="_blank" href="">Prize Draw & Tie-Break Process</a>
1212
</div>
1313
</div>

frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
<main class="flex-1 flex-column">
99
<mina-leaderboard-title></mina-leaderboard-title>
1010
<!-- <ng-container *ngTemplateOutlet="liveResultsInfo"></ng-container>-->
11-
<ng-container *ngTemplateOutlet="download"></ng-container>
11+
<!-- <ng-container *ngTemplateOutlet="download"></ng-container>-->
12+
@if (canDownloadCSV) {
13+
<ng-container *ngTemplateOutlet="downloadRestricted"></ng-container>
14+
}
1215
<mina-leaderboard-filters></mina-leaderboard-filters>
1316
<mina-leaderboard-table></mina-leaderboard-table>
1417
</main>
@@ -36,6 +39,17 @@
3639
</div>
3740
</ng-template>
3841

42+
<ng-template #downloadRestricted>
43+
<div class="flex-column">
44+
<div class="download-btns fx-row-vert-cent">
45+
<button class="fx-row-vert-cent h-lg mr-8" (click)="downloadAll()">
46+
<span class="mina-icon icon-200">download</span>
47+
<span>Download CSV</span>
48+
</button>
49+
</div>
50+
</div>
51+
</ng-template>
52+
3953
<ng-template #download>
4054
<div class="flex-column">
4155
<p class="mt-16 mb-8">Download Public Keys that qualify for the following Prizes:</p>

frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { LeaderboardService } from '@leaderboard/leaderboard.service';
4141
export class LeaderboardPageComponent extends StoreDispatcher implements OnInit, AfterViewInit {
4242
isExpanded = false;
4343
showBanner: boolean = false;
44+
canDownloadCSV = localStorage.getItem('download_leaderboard') === 'true';
4445

4546
private readonly SCROLL_THRESHOLD = 100;
4647
@ViewChild('scrollContainer') private scrollContainer!: ElementRef;
@@ -90,4 +91,8 @@ export class LeaderboardPageComponent extends StoreDispatcher implements OnInit,
9091
downloadMostProducedBlocks(): void {
9192
this.leaderboardService.downloadMostProducedBlocks();
9293
}
94+
95+
downloadAll(): void {
96+
this.leaderboardService.downloadAll();
97+
}
9398
}

frontend/src/app/features/leaderboard/leaderboard.routing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const routes: Routes = [
2929
},
3030
{
3131
path: '**',
32-
redirectTo: '',
32+
redirectTo: 'leaderboard',
3333
},
3434
];
3535

frontend/src/app/features/leaderboard/leaderboard.service.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { HeartbeatSummary } from '@shared/types/leaderboard/heartbeat-summary.ty
44
import { collection, collectionData, CollectionReference, Firestore, getDocs } from '@angular/fire/firestore';
55
import { WebNodeService } from '@core/services/web-node.service';
66
import { getElapsedTimeInMinsAndHours } from '@shared/helpers/date.helper';
7+
import { ONE_THOUSAND, toReadableDate } from '@openmina/shared';
78

89
@Injectable({
910
providedIn: 'root',
@@ -35,7 +36,7 @@ export class LeaderboardService {
3536
return ({
3637
publicKey: score['publicKey'],
3738
blocksProduced: score['blocksProduced'],
38-
isActive: score['lastUpdated'] > Date.now() - 120000,
39+
isActive: score['lastHeartbeat'] * ONE_THOUSAND > (Date.now() - 12000),
3940
uptimePercentage: this.getUptimePercentage(score['score'], this.maxScoreRightNow),
4041
uptimePrize: false,
4142
blocksPrize: false,
@@ -232,4 +233,50 @@ export class LeaderboardService {
232233

233234
URL.revokeObjectURL(url);
234235
}
236+
237+
async downloadAll(): Promise<void> {
238+
const querySnapshot = await getDocs(this.scoresCollection);
239+
const scoresData: any[] = [];
240+
241+
querySnapshot.forEach((doc) => {
242+
scoresData.push({ id: doc.id, ...doc.data() });
243+
});
244+
245+
const csvRows = [];
246+
247+
let filteredData = scoresData
248+
.map(row => ({
249+
publicKey: row.publicKey,
250+
score: row.score + ' / ' + this.maxScoreRightNow,
251+
uptime: this.getUptimePercentage(row.score, this.maxScoreRightNow) + '%',
252+
uptimeTime: row.score,
253+
producedBlocks: row.blocksProduced,
254+
lastUpdated: toReadableDate(row.lastUpdated * ONE_THOUSAND),
255+
}));
256+
filteredData = [...filteredData].sort((a, b) => b.uptimeTime - a.uptimeTime);
257+
258+
const headers = ['publicKey', 'score', 'uptime', /*'lastUpdated',*/ 'producedBlocks'].map(header => this.camelCaseToTitle(header));
259+
csvRows.push(headers.join(','));
260+
261+
// Map rows
262+
filteredData.forEach((row: any) => {
263+
const values = headers.map(header => {
264+
const key = header.charAt(0).toLowerCase() + header.slice(1); // Convert to corresponding key
265+
const escape = ('' + row[key.replace(' ', '')]).replace(/"/g, '\\"');
266+
return `"${escape}"`;
267+
});
268+
csvRows.push(values.join(','));
269+
});
270+
271+
const csvString = csvRows.join('\n');
272+
const blob = new Blob([csvString], { type: 'text/csv' });
273+
const url = URL.createObjectURL(blob);
274+
275+
const link = document.createElement('a');
276+
link.href = url;
277+
link.download = `export_${new Date().toISOString().replace(/:/g, '-')}.csv`;
278+
link.click();
279+
280+
URL.revokeObjectURL(url);
281+
}
235282
}

0 commit comments

Comments
 (0)