Skip to content

feat(dashboard): dashboard UI fixes and hybrid attribution in the simulator - #439

Open
AnkitKmrGupta wants to merge 5 commits into
mainfrom
feat-dashboard-simulator-enhancements
Open

AnkitKmrGupta wants to merge 5 commits into
mainfrom
feat-dashboard-simulator-enhancements

Conversation

@AnkitKmrGupta

@AnkitKmrGupta AnkitKmrGupta commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Dashboard polish plus two correctness fixes. On the Overview every card counted a single routing kind, so a merchant's hybrid traffic was invisible; in the Decision Simulator every hybrid decision was attributed to SR, including the ones the static rule made.

Overview — every card counted one routing kind

The page sends no routing_kind, so the endpoint defaulted to multi-objective and a merchant's hybrid traffic never appeared. On a merchant doing 9,271 hybrid and 2,065 decide-gateway requests, Requests read 2,065, Gateway activity read "2,065 decisions across 2 gateways", and Auth rate was computed over the decide-gateway slice alone.

Errors was separately wrong: it summed top_errors, which the server caps at the five largest groups, so a merchant failing in more ways than the card lists saw a total smaller than the list under it.

  • A merchant-wide totals on the overview response — request_count, requests_by_route, error_count, auth_rate, gateway_volumes — counted across both decision flow types (ALL_DECISION_FLOW_TYPES). The Overview cards read from it.
  • The fields beside it keep their per-kind scoping, because the Analytics tabs depend on it: route_hits still scopes /update_gateway to the payments one kind decided — counting the endpoint outright would print the same merchant-wide number on both tabs — while totals.requests_by_route counts every call to it.
  • error_summaries::load_total runs the summary query's filters with no GROUP BY and no LIMIT, so a total and the list it sits above cannot disagree. The Errors KPI reads it too; it previously used a routing-kind-scoped count while top_errors was not scoped, so one response could contradict itself.
  • Score-snapshot limit 5 → 100. The page averages snapshots per gateway, and a limit that small cut the low scorers out of every average and could drop a struggling gateway off Gateway health entirely — the gateway the card exists to surface.
  • The page no longer calls /analytics/routing-stats: one analytics request instead of two, and every figure comes from one response.

On the page:

  • An Auth rate card, from the auth_rate the response already carried and nothing rendered. Its subtitle names the denominator as resolved outcomes — pending statuses are in neither the success nor the failure bucket, so it never matched the endpoint's call count and "reported" implied it did.
  • Exact figures instead of compact ones. "2K" for 2,063 hides what the number is read for, and a run of errors growing 1,400 → 2,400 never changed the label. Compact takes over past six digits, with the exact value on hover.
  • Requests breaks down per endpoint, so the total shows what it is made of.
  • All three card rows share one column geometry.
  • Gateway names are title-cased through humanizeAuditValue (already used by Decision Audit) rather than shouted.
  • "N more in Analytics" / "N more in Decision Audit" where a list is truncated.

Simulator — hybrid decisions were all credited to SR

srBased = total - costWon treated anything that wasn't a cost override as SR-decided. On a hybrid run the rule answers whenever the dynamic half returns nothing, so a run whose Transaction Log was entirely RULE_OUTPUT still read 208 of 208 SR-based decisions.

  • Decisions now fall into three buckets — SR, rule, cost — that sum to the total. A Rule-based decisions tile shows the count with its charged/failed split, and appears as soon as the hybrid endpoint is selected rather than waiting for the first rule-decided payment.
  • FAAR and NAR no longer count rule-decided payments. The rule named the gateway before SR scored anything, so charging one says how that gateway performed, not how well routing chose — counting them reported the rule's own success rate as FAAR. A run with no SR or cost decision now reads . Both tooltips say what the denominator is.
  • The Transaction Log's Routing column rendered RULE_OUTPUT as raw text while the other approaches had badges. It is now a Rule Based badge, indigo — violet is stripe's connector colour on the same page, emerald is Cost Based, amber is Hedging, red is FAILURE. Label and badge come from one routingApproachDisplay() shared by the cell and the column filter, replacing a duplicated ternary chain.
  • The Endpoint select clipped: Hybrid routing (/routing/hybrid) is wider than the 170px field and a native select paints the overflow over its own arrow. The option text is now the label; the path moved to hover.

Analytics header

Title, view tabs and the time range shared one row. The tab strip grows with every routing view added, so it squeezed the range pills until Custom wrapped onto a line of its own.

  • The view switcher is a menu (SelectMenu, new) — one fixed-width trigger whatever the list holds, so a new routing view can never squeeze that row again.
  • One ANALYTICS_VIEW_TABS list now drives the AnalyticsView union, the ?view= values the URL accepts, release gating and the menu itself. Adding a routing view is one line plus its panel.
  • unhandledView(view: never) closes the panel chain, so a view added without a panel is a build error instead of a tab that quietly renders the panel next to it.
  • Dropped a redundant URL check — the visible-tab lookup already rejects an unknown ?view=.

Other UI fixes

  • Time range — presets no longer wrap, so Custom stops being stranded on its own line.
  • Buttons — the focus ring moved from :focus to :focus-visible. A mouse click used to leave a stuck halo on the clicked control; keyboard focus still rings. App-wide.
  • Members — the role select set padding-right: 12px, narrower than the native arrow, so "Member" ran underneath it.
  • Decision Audit — the match count showed the current page's row count, reading 12 of 1,556 on page 1 and page 4 alike. It now shows the page's slice: 37–48 of 1,556.
  • Routing Hub — strategy icons now match the sidebar (Multi-objective and Rule based were a target and a signpost there, an arrow and a book in the nav).
  • A/B Testing — the audit deep link respects BASE_URL.

Backend

  • update-gateway-score records an analytics event on the skip path (update_gateway_score_skipped, stage score_skipped) carrying the request and the reason it was skipped, so a skipped score appears in Decision Audit instead of vanishing. Both label maps resolve the new stage to "Update Gateway".
  • Merchant config migrates cost-savings merchants off the legacy feature key: a merchant enabled before the rename was listed only in the old row, so read_effective reported it off while the decider's own fallback kept routing on cost — a disable silently didn't stick. The toggle now writes the current key and clears the legacy entry.

Verification

  • tsc --noEmit clean, vite build green.
  • cargo fmt --check, cargo clippy and cargo test --lib (510 passed) run locally. The single clippy warning is pre-existing, in volume_commitment.rs.
  • Overview auth-rate figures cross-checked against ClickHouse directly: 9,762 CHARGED + 670 FAILURE = the 10,432 the card reports.

Comment thread src/routes/merchant_account_config.rs Outdated
Comment thread src/routes/merchant_account_config.rs Outdated
jagan-jaya
jagan-jaya previously approved these changes Sep 18, 2026
@AnkitKmrGupta
AnkitKmrGupta force-pushed the feat-dashboard-simulator-enhancements branch from 28f8d7d to 3a86b94 Compare September 19, 2026 09:28
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.

2 participants