Skip to content

fix(frontend): scope Home upcoming purchases to the provider chip - #1365

Merged
IAmJSD merged 5 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-713
Oct 24, 2026
Merged

IAmJSD merged 5 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-713

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

The failed-fetch half of this issue was handled by #1186. The other half remained: the Home "Upcoming Scheduled Purchases" widget ignored the Provider and Account chips, so with Provider = AWS it still listed Azure purchases. This PR applies the provider chip client-side and adds a note that the account chip does not apply to this widget, since the rows carry no account and the endpoint takes no filter parameters.

Refs #713
Partial: account scoping needs filter parameters on GET /api/dashboard/upcoming (backend), after which the frontend can pass the chip state and drop the note.

What changed

  • New frontend/src/lib/upcoming-scope.ts:
    • scopeUpcomingToProvider() filters rows by the provider chip (empty or "all" keeps everything, case-insensitive).
    • upcomingEmptyMessage() names the provider when the filter empties the list.
    • UPCOMING_ACCOUNT_SCOPE_NOTE.
  • frontend/src/dashboard.ts renderUpcomingPurchases(): applies the provider scope (the Retry path goes through the same renderer), builds the empty state with DOM calls instead of innerHTML, and when an account chip is active and rows exist, shows a muted note above the cards. The View Details index is built from the scoped rows.
  • New src/__tests__/dashboard-upcoming-scope-713.test.ts.

Why this approach

The API rows already carry provider, so provider scoping is a correct, standalone frontend change. Account scoping cannot be done client-side, and getUpcomingPurchases (internal/api/handler_dashboard.go) takes no filter parameters. Rather than send query params the backend ignores and guess the future contract, the widget says the account filter does not apply, which removes the silent disagreement. The new logic lives in a small lib module because dashboard.ts is already about 1300 lines.

Screenshots

Before is main (a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API. The chips are set via the URL (?provider=...&account=...); KPI tiles and charts are hidden in the shots only so the chips and the widget fit in one frame.

Before After
provider-and-account-chips before provider-and-account-chips after
provider-no-match before provider-no-match after
  1. Provider = AWS and Account = AWS Prod: before, the widget still lists the Azure VM reservation with no hint that the account chip is ignored. After, only the AWS rows remain and a muted note says the account filter does not apply here.
  2. Provider = GCP: before, the widget lists AWS and Azure purchases. After, it shows "No upcoming scheduled purchases for GCP".

Verification

Run on this branch's head 210de5c7 on top of main a14dc8f2.

  • npm run lint: 0 errors (142 pre-existing warnings, none in changed files). Typecheck and build pass.
  • Jest: 110 suites, 3495 passed, 1 skipped. New dashboard-upcoming-scope-713.test.ts: 9 tests (helpers, plus the loadDashboard render path with no chips, provider chip, provider with no matches, account note, account plus empty, Retry applying the scope); also passes with --randomize.
  • Playwright: 157 passed. No new e2e spec, since this is data scoping and jest drives the real render path.
  • Secrets scan clean, planted key flagged. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.

Notes for review

  • Backend follow-up: add filter parameters to GET /api/dashboard/upcoming (for example provider and account_ids, as /dashboard/summary takes), then pass the chip state from the frontend and remove the note.
  • Copy to check: "The account filter does not apply here: showing upcoming purchases for all accounts you can access." and "No upcoming scheduled purchases for GCP".
  • A row's provider is the plan's first service provider (upcomingFromExecution), so a hypothetical multi-provider plan would be filtered by that one provider.

Overlaps

Note for the UI owner

This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.

🤖 Generated with Claude Code

The upcoming widget kept listing every provider's scheduled purchases
while the rest of Home followed the topbar chips, so it could disagree
with the page it sits on. GET /api/dashboard/upcoming takes no filter
parameters, but every row carries its plan's provider, so the provider
chip is applied client-side and an empty result names the filter
("No upcoming scheduled purchases for GCP"). Rows carry no account, so
the account chip cannot be applied here; a short note above the rows
says so instead of silently ignoring it. Both can move server-side once
the endpoint accepts the chip state.

The Retry path from the earlier error-state fix goes through the same
renderer, so it applies the same scope.

Refs LeanerCloud#713

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added priority/p2 Backlog-worthy triaged Item has been triaged severity/high Significant harm urgency/this-quarter Within the quarter impact/all-users Affects every user effort/s Hours type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

IAmJSD and others added 4 commits October 24, 2026 16:59
Conflicts:
- frontend/src/dashboard.ts: import block. This branch added the
  lib/upcoming-scope import; main (LeanerCloud#1323) added formatTrendTooltipTitle
  to the chart-utils import. Kept both: the upcoming-scope import plus
  the widened chart-utils import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No textual conflicts. Checked main's LeanerCloud#1397 (getUpcomingPurchases now
typed as the { purchases } envelope; dashboard UpcomingPurchase is an
alias of the API type): lib/upcoming-scope.ts and renderUpcomingPurchases
still typecheck against it, and the provider scope applies to
data?.purchases on both the initial load and the Retry path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- frontend/src/dashboard.ts: import block only. Kept this branch's lib/upcoming-scope import and took main's widened chart-utils import (formatSparseTrendMessage, MIN_TREND_POINTS from LeanerCloud#1334). renderUpcomingPurchases merged cleanly with main's LeanerCloud#1376 formatter changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts: main's LeanerCloud#1387 (ladder timeline) merged cleanly and touches no file this branch changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE b1d794c1386ba33913bd70db45f765e61eefac28

Blocking:

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at b1d794c1 merged onto current main (19534fca; merge-tree 7a7ab058; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree ab77aa6e, main plus 5 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 4248 passed, 4249 total), and the full Playwright suite (247 passed).
  • CI is green on b1d794c1 (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 22:06
@IAmJSD
IAmJSD merged commit 030c5d4 into LeanerCloud:main Oct 24, 2026
29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at b1d794c1.

IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Brings in LeanerCloud#1365, LeanerCloud#1381, LeanerCloud#1378 and LeanerCloud#1389. No conflicts; index.html
auto-merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/dashboard.ts: import block only. Kept this branch's
  buildKpiTile import (lib/kpi-tile) and main's upcoming-scope import
  (LeanerCloud#1365) side by side. No body conflicts; main's onboarding, upcoming
  scope and chart a11y changes merged cleanly around the kpi-tile loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/dashboard.ts:
  - imports: kept this branch's filterByEnabledProviders import and all of
    main's new imports (upcoming-scope LeanerCloud#1365, sparse-trend helpers LeanerCloud#1334,
    chart-a11y LeanerCloud#1381, first-run empty state LeanerCloud#1389).
  - loadDashboard Promise.allSettled: combined both sides into five
    fetches; this branch's loadEnabledProviders() (LeanerCloud#180) plus main's
    listAccountsMinimal() for the first-run onboarding card (LeanerCloud#1389).
  - call site: kept renderDashboardSummary(summaryData, recs, recsCount)
    and main's renderHomeOnboarding(...). The onboarding check uses the
    Enabled-Providers-filtered recs, matching the Opportunities first-run
    check, which also runs on the preference-filtered list.
  - helpers: kept both loadEnabledProviders() and main's
    renderHomeOnboarding(); renderDashboardSummary keeps this branch's
    recsCount parameter.
  - savings tile detail: this branch's filtered count with main's
    NO_VALUE placeholder (LeanerCloud#1376): recsCount ?? total_recommendations ??
    NO_VALUE; Active Commitments takes main's NO_VALUE.
- frontend/src/recommendations.ts: import block; main's NO_VALUE added to
  the utils import, kept this branch's enabled-providers import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p2 Backlog-worthy severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant