Skip to content

Fix test suite, add Swapter/NYM/Revolut plugins, and add isolated v2 dashboard - #230

Open
j0ntz wants to merge 3 commits into
jon/reports-server-umbrellafrom
jon/reports-v2-dashboard
Open

Fix test suite, add Swapter/NYM/Revolut plugins, and add isolated v2 dashboard#230
j0ntz wants to merge 3 commits into
jon/reports-server-umbrellafrom
jon/reports-v2-dashboard

Conversation

@j0ntz

@j0ntz j0ntz commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Batched integration branch for the reports-server umbrella: the broken test suite is repaired first (everything else depends on it), then three partner plugins, then the isolated v2 dashboard and partner-reported revenue. One commit per unit of work.

Asana: https://app.asana.com/1/9976422036640/project/1213843652804305/task/1217040330890732

Commits

Commit What
Fix broken mocha test suite Corrects the util.test.ts import and stale analytics fixtures so npm test passes. Unblocks every landing in this repo.
Add CI job to run the test suite New .github/workflows/test.yml runs npm test on every PR, so the suite cannot silently rot again.
Add Swapter partner plugin Supersedes partner PR #219 and our earlier recreation #224.
Add NYM Swap (nymswap) reporting plugin New plugin, no prior PR.
Add Revolut fiat payment provider Supersedes partner fork PR #211.
Add v2 dashboard config endpoint and rev-share rates Isolated GET /v2/config.
Add isolated v2 reports dashboard The /v2/ dashboard itself.
Track partner-reported revenue through to the dashboard StandardTx.revenueUsd / revenueSource, summed through the cache.

Superseded PRs stay open until this lands: #211 and #219 are partner-owned fork branches (not ours to close), #224 is already closed.

Providers in scope but not in this branch: nexchange, Bridgeless, Simplex. They remain open on the umbrella.

v2 dashboard

v1 is untouched. v2 has its own entry point (src/demoV2/), its own parcel bundle (built into dist/v2/ by a separate parcel invocation), and its own URL. v1's parcel source, routes, components and build output are unchanged. Shared v1 code is duplicated into v2 rather than refactored.

  • src/demoV2/index.html — the full prototype UI (summary card; Providers and Currency pairs sections, each with a trend chart, a hover-linked donut + ranked-bar share card, and a detail table; global range/type/provider/pair filters; top-8-plus-Other color rules; paginated pair table). The rendering/interaction engine is ported verbatim; only the data layer changed.
  • Real API data — on load the dashboard reads the apiKey cookie (or ?apiKey=), validates via GET /v1/getAppId, then fetches GET /v2/config, GET /v1/getPluginIds, and one POST /v1/analytics (day buckets, last 24 months, all providers in a single call) and builds the per-provider daily series the engine consumes.
  • Auth = v1's apiKey model, minus the hang. v1's known bug (getAppId returns plain text on a 400, response.json() throws, the mount promise dies silently and the page spins forever) is fixed: the response is checked before parsing and a bad/missing key redirects to a key-entry screen. No new auth system.
  • src/routes/v2/getConfig.ts — new isolated GET /v2/config returning per-partner rev-share rates and fiat/swap classification. No v1 route is modified.
  • src/demoV2/README.md — what v2 is, the endpoints it consumes, and local build/serve steps.

Revenue

Where a partner's API reports Edge's actual fee per order, the plugin stores it on the transaction as revenueUsd with revenueSource: 'reported', the cache engine sums it into the analytics buckets, and the dashboard uses it directly, marked with a check in the provider table. That figure is a fact about the order and is never recomputed. Revolut is the first reporter (fees_partner_currency.partner_fee, taken only on settled orders and only when the settlement currency is USD).

Everywhere a partner reports nothing, the dashboard falls back to an estimate of volume * revShareRate, computed at read time. Rates live on the reports_apps app doc, beside each partner's apiKeys, not in config.json: the rate is a property of the app-partner deal, it is commercial terms, and this repo is public. A partner with no rate contributes 0. Correcting a rate fixes history immediately, while reported figures stay immutable.

Cache docs written before this change lack the revenue field until the next cache rebuild, which fills history from stored txs with no partner re-query.

Testing

npm test passes (and now runs in CI on every PR).

v2 verified end to end against a local CouchDB and the real API (npm run build.distnpm run start.api/v2/). See attached screenshots:

  • Providers section rendering live data (summary tiles, top-8+Other stacked trend, sparklines).
  • Currency pairs section + provider detail table (fiat/swap badges, share bars, per-pair charts, sparklines).
  • Bad/missing apiKey redirects to the key-entry screen ("That API key was not recognized") instead of hanging.

Endpoint checks: getAppId valid→appId / invalid→400, /v2/config valid→JSON / invalid→401, getPluginIds→registered providers, analytics POST→expected bucket shape. tsc is clean; both v1 and v2 parcel bundles build. No browser console errors.

Note: the dashboard screenshots predate the revenue commit, so the provider table in them has no reported-revenue check column.

@j0ntz

j0ntz commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence (v2 dashboard on live local /v1 data)

providers

providers

pairs and detail

pairs and detail

badkey gate

badkey gate

Captured by the agent's in-app test run (build-and-test).

@j0ntz
j0ntz force-pushed the jon/reports-v2-dashboard branch 2 times, most recently from 77d5d6d to 25b646d Compare July 31, 2026 22:36
j0ntz added 3 commits July 31, 2026 15:54
Adds an isolated /v2/config route returning per-provider rev-share rates and
fiat/swap classification. No v1 route is modified.

The rates live on the app doc in reports_apps, as an optional revShareRate
beside each partner's apiKeys. The rate is a property of the app-partner deal,
so it is per app AND per partner, and it sits with the credentials that define
the relationship: onboarding a partner is one doc edit, with no separate rates
map to forget. The rates are commercial terms and this repo is public, so they
are never committed or placed in config.json. A partner without one contributes
0 estimated revenue.
Ports the redesign prototype into src/demoV2 as a standalone dashboard
served at /v2/, built into dist/v2 by a separate parcel invocation so v1's
entry, bundle, and output are untouched. The prototype's sample-data layer
is replaced by the real /v1 API (getAppId auth, getPluginIds, one analytics
POST for all providers) plus /v2/config for rev-share and provider types.

A bad or missing apiKey now redirects to a key-entry screen instead of
hanging: getAppId returns plain text on 400, so the response is checked
before parsing and fetch errors are surfaced.
Some partner APIs report Edge's actual fee per order; estimating that same
number as volume times a configured rate discards a fact we already hold. This
adds the pair StandardTx.revenueUsd / revenueSource: the actual USD figure,
stored at ingest as a fact about the order, and the vocabulary that lets
downstream consumers tell a reported figure from a derived one.

Revolut is the first reporter: fees_partner_currency.partner_fee, pre-converted
to USD by Revolut, taken only on settled orders (an unsettled attempt's fee is
not revenue) and only when the settlement currency is USD (a conversion this
plugin cannot do honestly).

The cache engine sums reported revenue into the analytics buckets, and the v2
dashboard uses it directly wherever a provider reports, marked with a check in
the provider table. Providers that report nothing keep the estimate, computed
at read time from the app doc's per-partner revShareRate, so correcting a rate
fixes history immediately while reported figures stay immutable. Pair rows
attribute bucket revenue proportionally to volume share, the same rule the
existing tx attribution uses.

The revenue keys are optional at the type level rather than spelled out as
explicit undefineds across every partner plugin; the cleaner still validates
them when present. Cache docs written before this field lack it until the next
cache rebuild, which fills history from stored txs with no partner re-query.
@j0ntz
j0ntz force-pushed the jon/reports-v2-dashboard branch from 25b646d to cf6000c Compare July 31, 2026 23:03
@j0ntz
j0ntz changed the base branch from master to jon/reports-server-umbrella July 31, 2026 23:03
@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@j0ntz j0ntz changed the title Add isolated v2 reports dashboard Fix test suite, add Swapter/NYM/Revolut plugins, and add isolated v2 dashboard Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant