Skip to content

feat(api,ui): wire Download CSV into the Runtime page#6500

Merged
JSONbored merged 1 commit into
JSONbored:mainfrom
luciferlive112116:feat/runtime-csv-export-6392
Jul 17, 2026
Merged

feat(api,ui): wire Download CSV into the Runtime page#6500
JSONbored merged 1 commit into
JSONbored:mainfrom
luciferlive112116:feat/runtime-csv-export-6392

Conversation

@luciferlive112116

Copy link
Copy Markdown
Contributor

Closes #6392

/runtime was the one Explorer list page with no CSV export — the last unshipped installment of the "wire Download CSV into " series (#3403/#3404/#3408/#5562/#5665).

Screenshots

Download CSV now sits beside Share view, the same slot and order every sibling page's ActionBar uses. Fixed-viewport captures (never fullPage), theme forced via mg-theme + reload; before = main, after = this branch.

Viewport · Theme Before After
Desktop · Light
before

after
Desktop · Dark
before

after
Tablet · Light
before

after
Tablet · Dark
before

after
Mobile · Light
before

after
Mobile · Dark
before

after

This was not a frontend-only job

The issue asks to "confirm (and add if missing) format=csv support". It was missing — /api/v1/runtime rejected every query param (validateQueryParams(url, [])), so ?format=csv 400'd before it could reach the handler. Verified against the live route before changing anything:

before:  /api/v1/runtime?format=csv  ->  400
after:   /api/v1/runtime?format=csv  ->  200  text/csv  attachment; filename="runtime-versions.csv"
         header row: spec_version,block_number,observed_at

Backend

  • handleRuntime accepts the one param via the same validateEntityQuery(url, ["format"]) idiom its neighbours in this file already use — so ?format=bogus and any unknown param are still rejected (?limit=5 → 400).
  • The CSV exports data.transitions with the three columns the page's table actually renders (Spec Version | Block | Observedspec_version, block_number, observed_at), satisfying the issue's "CSV must match the on-screen table's columns". The rollup fields (current_spec_version, coverage_from_block/at) describe the series rather than any row, so they stay JSON-only — mirroring how chain-signers/chain-fees export their leaderboard and keep totals out of the CSV.
  • A cold store yields transitions: [] → a header-only CSV, never an error.
  • The route contract gains csvResponse + the format parameter, so the generated OpenAPI documents both (200 now advertises application/json and text/csv).

Frontend

  • DownloadCsvButton in runtime.index.tsx's PageHero.actions. /api/v1/runtime is a single whole-window aggregate with no filters to carry, so the export URL needs no params beyond format — unlike the filtered feeds, which spread their <page>QueryParams(search).

Tests

6 new cases in tests/request-handlers-entities.test.mjs. They are real regression tests — I reverted the handler and confirmed they fail without it: the CSV body/headers/filename, the JSON default (asserting the rollup stays JSON-only), ?format=json, the cold header-only CSV, ?format=bogus → 400, and that unknown params are still refused.

The Playwright capture asserts the control exists rather than just photographing it:

PASS: Download CSV control present (1 match(es))
Share view control present: true
before: Download CSV controls on /runtime = 0
tests/request-handlers-entities.test.mjs  passed
tests/contracts.test.mjs                  passed
tests/api-coverage.test.mjs               passed
validate:contract-drift / validate:types / validate:openapi  -> pass

Derived artifacts (openapi.json, types.d.ts, api-index.json, packages/contract/index.d.ts) regenerated and verified byte-identical to a fresh build after rebasing onto a main that moved 3 commits mid-work. eslint clean, prettier --check clean, pushed at behind: 0.

/runtime was the one Explorer list page with no CSV export, despite rendering
a plain newest-first table structurally identical to every sibling feed
(blocks, extrinsics, events, sudo, validators, providers, subnets, ...). It is
the last unshipped installment of the "wire Download CSV into <page>" series
(JSONbored#3403/JSONbored#3404/JSONbored#3408/JSONbored#5562/JSONbored#5665).

The frontend alone could not close it: /api/v1/runtime rejected every query
param (validateQueryParams(url, [])), so ?format=csv returned 400 before it
could reach the handler -- verified against the live route before changing
anything.

Backend:
- handleRuntime accepts the one param, via the same validateEntityQuery(url,
  ["format"]) idiom its neighbours in this file already use, so ?format=bogus
  and any unknown param are still rejected.
- ?format=csv exports data.transitions with the three columns the page's table
  renders (Spec Version | Block | Observed -> spec_version, block_number,
  observed_at), satisfying the issue's "CSV must match the on-screen table".
  The rollup fields (current_spec_version, coverage_from_block/at) describe the
  series rather than any row, so they stay JSON-only -- mirroring how
  chain-signers/chain-fees export their leaderboard and keep totals out of the
  CSV. A cold store yields transitions: [] -> a header-only CSV, never an error.
- The route contract gains csvResponse + the format parameter, so the generated
  OpenAPI documents both the param and the text/csv response.

Frontend:
- DownloadCsvButton in runtime.index.tsx's PageHero.actions, ahead of
  ShareButton, matching every sibling page's ActionBar order. /api/v1/runtime
  is a single whole-window aggregate with no filters to carry, so the export
  URL needs no params beyond format -- unlike the filtered feeds, which spread
  their <page>QueryParams(search).

The 6 regression tests fail on the unfixed handler (verified by reverting it),
covering the CSV body/headers/filename, the JSON default, ?format=json, the
cold header-only CSV, ?format=bogus, and that unknown params are still refused.
@superagent-security

Copy link
Copy Markdown

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.86%. Comparing base (632d986) to head (29f7ebb).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6500   +/-   ##
=======================================
  Coverage   97.85%   97.86%           
=======================================
  Files         174      174           
  Lines       22196    22199    +3     
  Branches     8798     8799    +1     
=======================================
+ Hits        21721    21724    +3     
  Misses         81       81           
  Partials      394      394           
Files with missing lines Coverage Δ
src/contracts.mjs 98.05% <ø> (ø)
workers/request-handlers/entities.mjs 98.18% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 17, 2026
@loopover-orb

loopover-orb Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-17 17:30:18 UTC

8 files · 1 AI reviewer · no blockers · CI green · unknown

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review: This PR changes guardrail-protected path(s): apps/ui/src/routes/runtime.index.tsx (matched apps/ui/**).

Review summary
This is a straightforward, well-scoped installment of the established 'wire Download CSV into <page>' pattern: it adds a `format` query param to `/api/v1/runtime`, exports the three on-screen columns via `csvResponse`, wires `DownloadCsvButton` into the UI in the same slot/order as sibling pages, and regenerates all contract artifacts (OpenAPI, api-index, contracts.mjs, both types.d.ts copies) consistently. Tests cover CSV export, the JSON-default path, cold-store header-only CSV, and rejection of unsupported/unknown params, matching the validation change from `validateQueryParams(url, [])` to `validateEntityQuery(url, ["format"])`. The rollup fields are correctly excluded from CSV (JSON-only), consistent with the stated convention for chain-signers/chain-fees.

Nits — 4 non-blocking
  • The `7,Allways` example row in the OpenAPI `text/csv` schema (public/metagraph/openapi.json) is copy-pasted boilerplate unrelated to the actual runtime columns (`spec_version,block_number,observed_at`) — worth fixing to avoid misleading API consumers.
  • The magic number `7` in the same stale example also appears duplicated into packages/contract/index.d.ts and public/metagraph/types.d.ts — same fix should propagate to all three.
  • Update the `text/csv` example in openapi.json/contracts.mjs to reflect the actual runtime CSV shape, e.g. `spec_version,block_number,observed_at\r\n423,8000000,2026-06-25T00:00:00.000Z`, then regenerate the derived types.d.ts/index.d.ts so the example is consistent everywhere.
  • Touches a guarded path — held for manual review — A maintainer must review and merge this change.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ⚠️ Gate result — Not blocking (Advisory; not blocking this PR.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #6392
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 172 registered-repo PR(s), 103 merged, 27 issue(s).
Contributor context ✅ Confirmed Gittensor contributor luciferlive112116; Gittensor profile; 172 PR(s), 27 issue(s).
Review context
  • Author: luciferlive112116
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, JavaScript, MDX, Rust, TypeScript
  • Official Gittensor activity: 172 PR(s), 27 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Triage stale or unlinked PRs.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/runtime desktop before /runtime
before /runtime
after /runtime
after /runtime
/runtime mobile before /runtime (mobile)
before /runtime (mobile)
after /runtime (mobile)
after /runtime (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 17, 2026
@JSONbored
JSONbored merged commit d587152 into JSONbored:main Jul 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runtime page has no CSV export, unlike every sibling chain-feed list page

2 participants