SEC-359: Replace compare app with server-rendered RPC performance overview#9
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR replaces the client-side RPC comparison UI with a server-rendered Grafana/Prometheus-backed dashboard: adds server Grafana/Prometheus helpers and queries, cached chain data aggregation, server async components (ChainTOC, ChainCard) with Suspense, redesigns the home page as an async server component, introduces a comprehensive metrics/scoring layer for provider ranking, and removes the old comparison pages and related client state/components. ChangesServer-Side Performance Dashboard Implementation
Sequence Diagram(s)sequenceDiagram
participant Browser
participant HomeServer as Home (Server)
participant FetchAllChains as fetchAllChains()
participant FetchChainData as fetchChainData()
participant GrafanaAPI as Grafana API
participant RpcPerformance as RpcPerformancePage
participant Browser
Browser->>HomeServer: GET / (async server component)
HomeServer->>FetchAllChains: await fetchAllChains(timeRange)
loop For each CHAINS entry with delay
FetchAllChains->>FetchChainData: fetchChainData(chain, timeRange)
alt GRAFANA_API_TOKEN set
FetchChainData->>GrafanaAPI: runPromQuery (p50/p95/p99 quantiles)
GrafanaAPI-->>FetchChainData: result rows
FetchChainData->>GrafanaAPI: runPromQuery (success rates)
GrafanaAPI-->>FetchChainData: result rows
FetchChainData->>GrafanaAPI: runPromRangeQuery (24h trend)
GrafanaAPI-->>FetchChainData: timeseries
else Token missing
FetchChainData->>GrafanaAPI: fetchPublicChainData (public panels)
GrafanaAPI-->>FetchChainData: parsed panels
end
FetchChainData-->>FetchAllChains: { providers, regions, leader, error }
end
FetchAllChains-->>HomeServer: allChainsData[]
HomeServer->>RpcPerformance: <Suspense> RpcPerformancePage { allChainsData, timeRange }
RpcPerformance->>RpcPerformance: enrichProviders(), computeScores(), sortByReliabilityThenLatency()
RpcPerformance-->>Browser: Render table, summary, share/time-range controls
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
f132307 to
f0dd123
Compare
- Home page rewritten as server-rendered RPC provider overview via Grafana Cloud Prometheus API; ISR revalidate=60 - Streaming SSR: per-ChainCard + Headline + ChainTOC <Suspense> boundaries so cards stream as their PromQL resolves - Bullet bars: p50/p95/p99 layered horizontal bars per provider, leader emerald, others slate - Inline-SVG 24h sparkline per provider row, no chart library - Regional heatmap per chain: provider × region p95 grid, HSL-tinted from chain-wide min/max - Headline strip: fastest-right-now, most-p95-wins, tail-latency leader across all chains - Mini-sparkline TOC: 4×2 chain grid with leader trend + anchor jumps - lib/grafana.js: runPromQuery + runPromRangeQuery - lib/chain-data.js: React.cache-wrapped per-chain fetcher, parallel queries, partial-data tolerant; TOC/Headline/Cards share results - lib/queries.js: providerByRegionQuery(chain, q) for any quantile + providerTrendQuery for range data - Route deletions: compare-single/, compare-double/, injection-start/, injection-result-double/ — all hit the backend's /scenarios/* SSRF surface; legacy URLs 301 to / in next.config.js - next.config.js: dropped wide-open /api/* CORS; CSP with frame-ancestors 'none', HSTS, nosniff, Referrer-Policy - store/store.js emptied; .env.sample drops NEXT_PUBLIC_BACKEND_APP_URL and adds GRAFANA_API_TOKEN - Dead components removed: Bento, Faq, Performance, ProtocolIcon (+ SVGs), ResultCard, orphaned icons
f0dd123 to
660888f
Compare
- Subtitle: drop jargon and middle dot, cap line width - Remove the dynamic headline stat line - Per-provider success rate (sum by provider) replaces the chain aggregate; shown under each provider, amber below 99.9% - Region codes shown as short country labels (US, DE, SG …) with full name in the column tooltip - Merge the region heatmap into the provider table as right-hand columns, sticky provider column; no duplicated provider names - Rename Trend column to "p95, 24h"; fix trend query comment
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.env.sample:
- Line 2: The env sample has spaces around the equals sign for assignments
(e.g., NEXT_PUBLIC_CLIENT_DOMAIN =), which can cause parsing issues; update the
assignment to remove spaces so it reads NEXT_PUBLIC_CLIENT_DOMAIN= and similarly
normalize other keys mentioned (line 8) to KEY= format throughout the file to
ensure valid .env syntax.
In `@next.config.js`:
- Line 26: Update the Referrer-Policy header value to a stricter setting to
reduce cross-origin data leakage: locate the header object with key
'Referrer-Policy' in next.config.js and change its value from
'no-referrer-when-downgrade' to 'strict-origin-when-cross-origin' (or a stricter
policy if desired), ensuring the header entry remains otherwise unchanged.
In `@src/lib/grafana.js`:
- Around line 20-23: The Grafana fetch call currently has no abort path; wrap
the request in an AbortController: create an AbortController before calling
fetch, pass controller.signal in the fetch options alongside headers:
authHeaders() and next: { revalidate }, start a timer (configurable timeout,
e.g. 3–5s) that calls controller.abort() on expiry, and clear the timer once the
response is received; also ensure any fetch errors from abort are
handled/translated appropriately where res is used so SSR doesn't hang.
- Around line 39-44: runPromRangeQuery currently passes start, end, and step
straight into fetchProm which can produce "undefined" query params; update
runPromRangeQuery to validate that start, end, and step are provided and not
undefined/null (and optionally coercible to valid values) before calling
fetchProm, and throw a clear error (e.g., TypeError or rejected Promise) if any
are missing; reference the runPromRangeQuery function and its call to
fetchProm('/api/v1/query_range', { query: promql, start, end, step }, opts) so
you enforce the checks there and only build/pass the params when valid.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 275bc1dd-78bd-4675-ac0f-7a4f46d89dbc
⛔ Files ignored due to path filters (25)
package-lock.jsonis excluded by!**/package-lock.jsonsrc/components/ProtocolIcon/aptos.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/arbitrum.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/aurora.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/avalanche.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/base.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/bitcoin.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/bnb.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/cronos.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/ethereum.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/fantom.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/filecoin.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/fuse.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/gnosis.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/harmony.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/near.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/optimism.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/polygonPOS.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/polygonZkEvm.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/ronin.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/scroll.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/solana.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/starknet.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/tezos.svgis excluded by!**/*.svgsrc/components/ProtocolIcon/zkSync.svgis excluded by!**/*.svg
📒 Files selected for processing (33)
.env.samplenext.config.jspackage.jsonsrc/app/compare-double/page.jssrc/app/compare-single/page.jssrc/app/injection-result-double/page.jssrc/app/injection-start/page.jssrc/app/page.jssrc/app/store/store.jssrc/components/Bento/Bento.jssrc/components/Chain/BulletBar.jssrc/components/Chain/ChainCard.jssrc/components/Chain/ChainCardSkeleton.jssrc/components/Chain/Sparkline.jssrc/components/ChainTOC.jssrc/components/ChainTOCSkeleton.jssrc/components/Faq/FaqAccordion.jssrc/components/Faq/FaqBasic.jssrc/components/Faq/post.mdxsrc/components/Header/Header.jssrc/components/Icons/BarChartIcon.jssrc/components/Icons/Customization.jssrc/components/Icons/ExplainResultsIcon.jssrc/components/Icons/GrafanaIcon.jssrc/components/Icons/Profiling.jssrc/components/Performance/Compare.jssrc/components/Performance/Preformance.jssrc/components/ProtocolIcon/ProtocolIcon.jssrc/components/ResultCard/ResultCard.jssrc/lib/chain-data.jssrc/lib/format.jssrc/lib/grafana.jssrc/lib/queries.js
💤 Files with no reviewable changes (18)
- src/components/Faq/post.mdx
- src/components/Faq/FaqAccordion.js
- src/components/Icons/Customization.js
- src/components/Faq/FaqBasic.js
- src/app/injection-start/page.js
- src/components/Performance/Compare.js
- src/components/Icons/GrafanaIcon.js
- src/components/Icons/BarChartIcon.js
- src/components/Bento/Bento.js
- src/components/Performance/Preformance.js
- src/components/Icons/ExplainResultsIcon.js
- src/components/Icons/Profiling.js
- src/components/ProtocolIcon/ProtocolIcon.js
- src/components/Header/Header.js
- src/components/ResultCard/ResultCard.js
- src/app/compare-single/page.js
- src/app/compare-double/page.js
- src/app/injection-result-double/page.js
…h timeout, range param validation
The CSP added in this PR blocked the Vercel toolbar (vercel.live + Pusher websockets), so comments/live feedback couldn't load or be submitted on preview deployments. Allowlist vercel.live and Pusher in script/style/font/ frame/connect-src, gated to VERCEL_ENV=preview so production stays locked down.
The latency table is w-full with auto layout, and the BulletBar column was the only flexible one, so it absorbed all leftover width. Chains with fewer region columns (e.g. TON) left more slack, stretching the bar and pushing the p50/p95/p99 numbers further from the provider name. Add a flexible spacer column before the region block so slack lands there instead, pinning every data column to a consistent width across all chains and keeping the region heatmap flush-right.
Each chain rendered its own auto-layout table, so column widths were computed per-card from content: chains with long provider names (TON) pushed the bars and numbers further right, and slack pooled unpredictably. The earlier spacer column made it worse by parking all slack between p99 and the heatmap. Switch to table-fixed with a generated colgroup: fixed widths on the metric columns (provider/bar/sparkline/p50/p95/p99) so they're identical in every card — bars start at the same x and numbers line up regardless of name length (long names truncate) — and auto-width region columns that split the remaining space, keeping the heatmap flush-right with no gap. Sparkline/p99 columns collapse to 0 width on mobile in step with their hidden cells.
Only consumer was the deleted app/store/store.js.
Accidental npm package, never imported.
- add semantic color tokens (fg/tier/signal/accent/panel) + keyframes/animation + mono font to tailwind.config; 'panel' avoids collision with Wedges 'surface' - convert components from inline styles to Tailwind classes - replace 16 JS-driven hover handlers with hover: classes - dynamic values (bar widths, brand row highlight) via CSS variables - remove inline <style> keyframe blocks; delete lib/theme.js
- convert all 20 .js files to .ts/.tsx; add src/lib/types.ts with domain types (Chain, Provider, ChainData, EnrichedProvider, ScoredProvider, AvailTier) - type the data pipeline (grafana/queries/chain-data), metrics, and all component props - fix RootLayout props (was : any); type Prometheus responses, CSS-var styles - tsconfig: target es2017 (Map/Set iteration under strict), drop stale .js includes - passes strict type-check (noImplicitAny via strict) + lint clean
pnpm auto-created a build-approval stub with no 'packages:' field; pnpm 9 on
Vercel rejects it ('packages field missing or empty'). This is a single-package
repo, not a workspace. Deleted and gitignored.
…allbacks - score = 1 / mean_region((1/p95_region) * successRate_region^3), computed per region then averaged then inverted — matches the compare dashboard exactly - success rate is now per-region (queries: 'by (provider, source_region)'), carried as Provider.regionSuccess; display availability = mean of regions - grafanaScore: unscoreable provider -> Infinity (ranks last), NO latency-only substitution (was a silent ranking change); partial-data banner still surfaces - generateSummary: headline always names the actual #1 row (sorted[0]); no silent provider-swap. availTier is display-only now (docs corrected; no gating) - fix copy: 'three regions' -> 'all monitored regions'; table header P95 range now reflects 24h/7d instead of hardcoded 24h - drop dead range coercions ('?? 24h' / ': 24h') — type-guaranteed unreachable
The probes (compare-dashboard-functions Vercel cron) measure every 3 minutes (*/3), but the page claimed 'Updated every minute' with a pulsing dot and never auto-refreshed. Now: - FreshnessIndicator: router.refresh() every 180s (pauses on hidden tab, refreshes on revisit) so the data actually updates on the probe cadence - copy -> 'Live · updates every 3 min' (page + loading skeleton) - align Grafana query cache 60s -> 180s to match the measurement cadence
It sorts by the single combined grafanaScore (reliability folded in via SR^3), not a two-key reliability-then-latency sort. Name now matches behavior.
Prometheus labels carry tiers (Alchemy-Growth, Helius-Developer, TonCenter-WithAPIKey). Add providerDisplayName: strip everything before the first hyphen (generic — new tiers never break it) + a tiny casing-only map (quicknode->QuickNode, drpc->dRPC). Display-only via EnrichedProvider.displayName; raw label stays the row identity/key, so no silent tier-merging.
- layout: use metadataBase + relative OG URL instead of string concat (avoids "undefined/og-image.png" when NEXT_PUBLIC_CLIENT_DOMAIN unset) - RpcPerformancePage: move localStorage write out of useMemo into useEffect - next.config: enable reactStrictMode (verified no double-render issues)
…lpers - add isNum type guard; drop repeated Number.isFinite + as-number casts across metrics.ts and ProviderMetricsTable.tsx - merge BRAND + LOGO_MAP into one CHAIN_BRAND record (adding a chain now touches one display entry instead of two files) - extract resolveProtocol + rangeQuery into lib/url-params.ts; reuse in RpcPerformancePage, TimeRangeSwitcher, and loading.tsx No behavior change: tsc, lint, and production build all pass.
depcheck + grep confirmed unused (no source imports): - @iconicicons/react (icons now from @phosphor-icons/react) - react-google-charts, react-type-animation (old compare app) MDX stack removed — no .mdx files, no mdx-components, never wired into pageExtensions, so it was inert: - @mdx-js/loader, @mdx-js/react, @next/mdx - drop withMDX wrapper from next.config.js Build, tsc, and lint pass; bundle size unchanged (deps weren't shipped).
SEC-359
Summary
Replaces the legacy client-side compare app with a server-rendered RPC provider performance page sourced from the Grafana Cloud Prometheus API. Also migrates the codebase from JavaScript to TypeScript.
What it does
src/app/page.tsx,force-dynamic) fetches all 8 chains' provider metrics in parallel and passes them to a client component for instant protocol switching.src/lib/):grafana.ts— server-only Prometheus client (instant + range queries), bearerGRAFANA_API_TOKEN, 180s fetch revalidate, 10s request timeout.queries.ts— PromQL for per-provider/per-region p50/p95/p99 latency, success rate, and a p95 trend series (successes only;TEST_*providers excluded).chain-data.ts— shapes per-chain provider lists; failed queries degrade to partial/error flags rather than fabricating values.src/components/RpcPerformance/): provider table with availability %, a p50/p95/p99 latency bar, a p95 sparkline, and a per-region p95 heatmap; protocol chips, a 24h/7d switcher, a share link, a "how ranking works" tooltip, and a route-level loading skeleton (loading.tsx). Providers are ranked by a combined speed × reliability score.Removed
Legacy compare/injection routes (
/compare-single,/compare-double,/injection-start,/injection-result-double), the client store, and their components and icons.next.config.js301-redirects/compare-singleand/compare-doubleto/, and/dashboardto the public Grafana dashboard; removed routes otherwise 404.Security headers (
next.config.js)CSP (including
frame-ancestors 'none'), HSTS,X-Content-Type-Options: nosniff, andReferrer-Policy: strict-origin-when-cross-origin. Vercel preview-toolbar allowances are gated to the preview environment only.Config / deploy
.env.sample:GRAFANA_API_TOKEN(Grafana Cloud service account, Viewer role) andNEXT_PUBLIC_CLIENT_DOMAIN. Set both in Vercel for Production and Preview.reactStrictMode: true.Testing
npm run build,tsc --noEmit, andnext lintall pass.