Skip to content

Commit a7b09b3

Browse files
committed
Very basic RankingTable & styling fixes
1 parent ce0f44d commit a7b09b3

File tree

6 files changed

+247
-93
lines changed

6 files changed

+247
-93
lines changed

src/components/RankingTable.css

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.ranking-table {
2+
background-color: var(--section-background);
3+
border-radius: 8px;
4+
padding: 20px;
5+
box-shadow: var(--box-shadow);
6+
}
7+
8+
.ranking-table h2 {
9+
margin-bottom: 15px;
10+
color: var(--accent-color);
11+
}
12+
13+
.ranking-table table {
14+
width: 100%;
15+
border-collapse: collapse;
16+
font-size: 1rem;
17+
}
18+
19+
.ranking-table th,
20+
.ranking-table td {
21+
text-align: left;
22+
padding: 10px;
23+
border-bottom: 1px solid var(--darker);
24+
}
25+
26+
.ranking-table th {
27+
color: var(--button-text-color);
28+
background-color: var(--button-background);
29+
}
30+
31+
.ranking-table tbody tr:hover {
32+
background-color: var(--darker);
33+
cursor: pointer;
34+
}
35+
36+
.ranking-table td {
37+
color: var(--text-color);
38+
}
39+
40+
.ranking-table td:first-child {
41+
font-weight: bold;
42+
}

src/components/RankingTable.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import "./RankingTable.css";
4+
5+
const RankingTable = ({ orchestrators, selectedKPI }) => {
6+
const kpiLabel = {
7+
avgPrice: "Price",
8+
avgDiscoveryTime: "Discovery Time",
9+
avgRTR: "Performance (RTR)",
10+
avgSR: "Performance (SR)",
11+
};
12+
13+
const sortedOrchestrators = [...orchestrators].sort((a, b) => {
14+
if (!a[selectedKPI] || !b[selectedKPI]) return 0;
15+
return a[selectedKPI] - b[selectedKPI];
16+
});
17+
18+
return (
19+
<div className="ranking-table">
20+
<h2>Ranking Table</h2>
21+
<table>
22+
<thead>
23+
<tr>
24+
<th>#</th>
25+
<th>Orchestrator</th>
26+
<th>{kpiLabel[selectedKPI]}</th>
27+
</tr>
28+
</thead>
29+
<tbody>
30+
{sortedOrchestrators.map((orchestrator, index) => (
31+
orchestrator[selectedKPI] !== null && orchestrator[selectedKPI] >= 0.0 && <tr key={orchestrator.id}>
32+
<td>{index + 1}</td>
33+
<td>{orchestrator.name}</td>
34+
<td>
35+
{orchestrator[selectedKPI].toFixed(2)}
36+
</td>
37+
</tr>
38+
))}
39+
</tbody>
40+
</table>
41+
</div>
42+
);
43+
};
44+
45+
RankingTable.propTypes = {
46+
orchestrators: PropTypes.arrayOf(
47+
PropTypes.shape({
48+
id: PropTypes.string.isRequired,
49+
name: PropTypes.string.isRequired,
50+
avgPrice: PropTypes.number,
51+
avgDiscoveryTime: PropTypes.number,
52+
avgRTR: PropTypes.number,
53+
avgSR: PropTypes.number,
54+
})
55+
).isRequired,
56+
selectedKPI: PropTypes.oneOf(["avgPrice", "avgDiscoveryTime", "avgRTR", "avgSR"]).isRequired,
57+
};
58+
59+
export default RankingTable;

src/hooks/useProcessedData.js

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,32 @@ const useProcessedData = () => {
1212
}
1313

1414
const perRegionStats = {};
15-
1615
const orchestrators = Object.entries(apiData).map(([orchestratorId, orchestratorData]) => {
16+
const leaderboardResults = orchestratorData.leaderboardResults || {};
1717
const instances = Object.entries(orchestratorData.instances || {}).map(
1818
([instanceId, instanceData]) => {
19-
// Extract probedFrom, regions, and livepeer_regions
2019
const probedFrom = Object.keys(instanceData.probedFrom || {});
2120
const regions = Object.keys(instanceData.regions || {});
2221
const livepeerRegions = Object.keys(instanceData.livepeer_regions || {});
2322

24-
// Calculate lastPing as the maximum lastTime in probedFrom
2523
const lastPing = Object.values(instanceData.probedFrom || {}).reduce(
2624
(max, regionData) => Math.max(max, regionData.lastTime || 0),
2725
0
2826
);
2927

30-
// Calculate avgPerformance metrics (latestRTR, latestSR) by mapping livepeer_regions to leaderboardResults
31-
let totalRTR = 0;
32-
let totalSR = 0;
33-
let performanceCount = 0;
28+
const avgRTR = livepeerRegions.reduce((sum, region) => {
29+
const leaderboard = leaderboardResults[region];
30+
return leaderboard ? sum + (leaderboard.latestRTR || 0) : sum;
31+
}, 0) / (livepeerRegions.length || 1);
3432

35-
livepeerRegions.forEach((region) => {
36-
const leaderboard = orchestratorData.leaderboardResults?.[region];
37-
if (leaderboard) {
38-
totalRTR += leaderboard.latestRTR || 0;
39-
totalSR += leaderboard.latestSR || 0;
40-
performanceCount++;
41-
}
42-
});
33+
const avgSR = livepeerRegions.reduce((sum, region) => {
34+
const leaderboard = leaderboardResults[region];
35+
return leaderboard ? sum + (leaderboard.latestSR || 0) : sum;
36+
}, 0) / (livepeerRegions.length || 1);
4337

44-
const avgRTR = performanceCount > 0 ? totalRTR / performanceCount : null;
45-
const avgSR = performanceCount > 0 ? totalSR / performanceCount : null;
38+
const bestDiscoveryTime = Object.values(orchestratorData.regionalStats || {})
39+
.map((stats) => stats.avgDiscoveryTime || Infinity)
40+
.reduce((best, current) => Math.min(best, current), Infinity);
4641

4742
return {
4843
id: instanceId,
@@ -53,52 +48,68 @@ const useProcessedData = () => {
5348
regions,
5449
livepeer_regions: livepeerRegions,
5550
lastPing: lastPing || null,
56-
bestDiscoveryTime: Object.values(orchestratorData.regionalStats || {})
57-
.map((stats) => stats.avgDiscoveryTime || Infinity)
58-
.reduce((best, current) => Math.min(best, current), Infinity) || null,
59-
avgRTR,
60-
avgSR,
51+
bestDiscoveryTime: bestDiscoveryTime === Infinity ? null : bestDiscoveryTime,
52+
avgRTR: avgRTR || null,
53+
avgSR: avgSR || null,
6154
};
6255
}
6356
);
6457

65-
// Calculate orchestrator-level aggregates
6658
const avgPrice =
6759
instances.reduce((sum, inst) => sum + inst.price, 0) / (instances.length || 1);
68-
const avgRTR =
69-
instances.reduce((sum, inst) => sum + (inst.avgRTR || 0), 0) /
70-
(instances.filter((inst) => inst.avgRTR !== null).length || 1);
71-
const avgSR =
72-
instances.reduce((sum, inst) => sum + (inst.avgSR || 0), 0) /
73-
(instances.filter((inst) => inst.avgSR !== null).length || 1);
7460

75-
const bestDiscoveryTime = instances.reduce(
76-
(best, inst) => Math.min(best, inst.bestDiscoveryTime || Infinity),
77-
Infinity
78-
);
61+
const avgDiscoveryTime = Object.values(orchestratorData.regionalStats || {})
62+
.map((stats) => stats.avgDiscoveryTime || Infinity)
63+
.reduce((best, current) => Math.min(best, current), Infinity);
7964

80-
const validBestDiscoveryTime =
81-
bestDiscoveryTime === Infinity ? null : bestDiscoveryTime;
65+
const avgRTR =
66+
Object.values(leaderboardResults).reduce((sum, metrics) => sum + (metrics.latestRTR || 0), 0) /
67+
(Object.keys(leaderboardResults).length || 1);
68+
69+
const avgSR =
70+
Object.values(leaderboardResults).reduce((sum, metrics) => sum + (metrics.latestSR || 0), 0) /
71+
(Object.keys(leaderboardResults).length || 1);
8272

8373
return {
8474
id: orchestratorId,
8575
name: orchestratorData.name,
8676
avgPrice: avgPrice || null,
77+
avgDiscoveryTime: avgDiscoveryTime === Infinity ? null : avgDiscoveryTime,
8778
avgRTR: avgRTR || null,
8879
avgSR: avgSR || null,
89-
bestDiscoveryTime: validBestDiscoveryTime,
9080
instances,
9181
};
9282
});
9383

84+
// Calculate average values for normalization
85+
const avgDiscoveryTime =
86+
orchestrators.reduce((sum, orchestrator) => sum + (orchestrator.avgDiscoveryTime || 0), 0) /
87+
orchestrators.length;
88+
89+
const avgPrice =
90+
orchestrators.reduce((sum, orchestrator) => sum + (orchestrator.avgPrice || 0), 0) /
91+
orchestrators.length;
92+
93+
// Normalize discovery time and pricing
94+
orchestrators.forEach((orchestrator) => {
95+
orchestrator.normalizedDiscoveryTime =
96+
orchestrator.avgDiscoveryTime !== null
97+
? (avgDiscoveryTime - orchestrator.avgDiscoveryTime) / avgDiscoveryTime
98+
: null;
99+
100+
orchestrator.normalizedPrice =
101+
orchestrator.avgPrice !== null
102+
? (avgPrice - orchestrator.avgPrice) / avgPrice
103+
: null;
104+
});
105+
94106
// Process per-region stats
95107
Object.entries(apiData).forEach(([orchestratorId, orchestratorData]) => {
96108
Object.entries(orchestratorData.regionalStats || {}).forEach(([region, stats]) => {
97109
if (!perRegionStats[region]) {
98110
perRegionStats[region] = { totalAvgTime: 0, totalPrice: 0, count: 0 };
99111
}
100112

101-
// Aggregate per-region stats
102113
perRegionStats[region].totalAvgTime += stats.avgDiscoveryTime || 0;
103114

104115
const instancePrices = Object.values(orchestratorData.instances || {}).map(
@@ -115,14 +126,12 @@ const useProcessedData = () => {
115126
.flatMap((orchestrator) => orchestrator.instances.map((inst) => inst.price))
116127
.filter((price) => price !== null);
117128
const allDiscoveryTimes = orchestrators
118-
.map((orchestrator) => orchestrator.bestDiscoveryTime)
129+
.map((orchestrator) => orchestrator.avgDiscoveryTime)
119130
.filter((time) => time !== null);
120131
const allRTR = orchestrators
121-
.flatMap((orchestrator) => orchestrator.instances.map((inst) => inst.avgRTR))
132+
.map((orchestrator) => orchestrator.avgRTR)
122133
.filter((rtr) => rtr !== null);
123-
const allSR = orchestrators
124-
.flatMap((orchestrator) => orchestrator.instances.map((inst) => inst.avgSR))
125-
.filter((sr) => sr !== null);
134+
const allSR = orchestrators.map((orchestrator) => orchestrator.avgSR).filter((sr) => sr !== null);
126135

127136
const priceBuckets = createBuckets(allPrices, 10);
128137
const discoveryTimeBuckets = createBuckets(allDiscoveryTimes, 10);

src/pages/home.css

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,6 @@
1313
background-color: var(--darker); /* Give it a thin, blue line look for separation */
1414
}
1515

16-
html {
17-
overflow-x: hidden;
18-
}
19-
20-
h4 {
21-
font-size: 1.5rem;
22-
color: var(--weird);
23-
text-align: center;
24-
margin: 20px 0;
25-
transition: transform 0.2s, color 0.3s ease;
26-
}
27-
28-
h4:hover {
29-
transform: scale(1.1);
30-
color: var(--yellow);
31-
}
32-
3316
.loading-screen {
3417
display: flex;
3518
justify-content: center;
@@ -41,13 +24,24 @@ h4:hover {
4124
font-size: 1.8rem;
4225
}
4326

27+
.home-root {
28+
display: flex;
29+
flex-direction: column;
30+
height: 100vh;
31+
background-color: var(--body-background-color);
32+
overflow: none;
33+
}
34+
35+
.header-bar {
36+
background-color: var(--section-background);
37+
padding: 10px 0;
38+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
39+
z-index: 10;
40+
}
41+
4442
.tabs {
4543
display: flex;
4644
justify-content: center;
47-
margin: 20px 0;
48-
background: var(--section-background);
49-
padding: 10px;
50-
border-radius: 8px;
5145
}
5246

5347
.tab {
@@ -71,11 +65,17 @@ h4:hover {
7165
background: var(--button-hover-background);
7266
}
7367

68+
.scrollable-content {
69+
flex: 1;
70+
overflow-y: auto;
71+
padding: 20px;
72+
background-color: var(--section-background);
73+
}
74+
7475
.tab-content {
75-
margin: 20px;
76+
flex: 1;
7677
padding: 20px;
7778
background: var(--section-background);
78-
border-radius: 10px;
7979
}
8080

8181
.ranking-section,
@@ -86,3 +86,22 @@ h4:hover {
8686
background: var(--darker);
8787
border-radius: 8px;
8888
}
89+
90+
/* Scrollbar Styling */
91+
.scrollable-content::-webkit-scrollbar {
92+
width: 12px;
93+
}
94+
95+
.scrollable-content::-webkit-scrollbar-track {
96+
background: var(--scrollbar-track-color);
97+
}
98+
99+
.scrollable-content::-webkit-scrollbar-thumb {
100+
background: var(--scrollbar-thumb-color);
101+
border-radius: 6px;
102+
filter: brightness(0.8);
103+
}
104+
105+
.scrollable-content::-webkit-scrollbar-thumb:hover {
106+
background: var(--scrollbar-thumb-hover-color);
107+
}

0 commit comments

Comments
 (0)