Skip to content

feat(recommendations): server-side pushdown for service/region/account filters (closes #162) - #846

Merged
cristim merged 2 commits into
mainfrom
fix/162-wave14
Jul 19, 2026
Merged

cristim merged 2 commits into
mainfrom
fix/162-wave14

Conversation

@cristim

@cristim cristim commented May 28, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Wire service and region as SQL WHERE-clause parameters via buildRecommendationFilter so the backend prunes rows before returning them (issue ux(recommendations): server-side pushdown for Service / Region / numeric column filters #162)
  • Frontend extracts a single-value categorical selection from column-filter state and forwards it as a query param; multi-value or absent filters stay client-side via applyColumnFilters
  • commitAndRefresh() replaces the direct rerenderRecommendations() call in categorical popover commits -- re-fetches for service/region/account columns, re-renders in-place for all others

Filters wired

Filter Transport Backend clause
service ?service=ec2 WHERE service = $N
region ?region=us-east-1 WHERE region = $N
account existing account_ids existing WHERE cloud_account_id = ANY(...)

Test plan

  • go test github.com/LeanerCloud/CUDly/internal/api/... -- 1360 passed
  • cd frontend && npx tsc --noEmit -- no errors
  • cd frontend && npx jest --testPathPattern=recommendations -- 332 passed
  • Go: TestGetRecommendations_PushdownFilters covers service+region reach filter, account_id reaches filter, invalid service slug returns 400, invalid region name returns 400
  • TS: 3 new loadRecommendations tests -- single-value service pushed, single-value region pushed, multi-value service not pushed (stays client-side)

Summary by CodeRabbit

  • New Features
    • Service and region filters now push down to the backend when set to a single value for improved performance, while multi-value filters remain client-side.
    • Clearing categorical filters now triggers a fresh data fetch for server-pushdown columns.

Review Change Stack

@cristim cristim added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/few Limited audience effort/m Days type/feat New capability labels May 28, 2026
@cristim

cristim commented May 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 28, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f517937-2be3-4e7f-b421-14db66840cf0

📥 Commits

Reviewing files that changed from the base of the PR and between 90aad46 and 9cbef02.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/recommendations.ts
  • internal/api/handler_recommendations_test.go
📝 Walkthrough

Walkthrough

This PR implements server-side pushdown filtering for single-value service and region categorical filters in the recommendations view. When a user selects exactly one service or region, that filter is forwarded to the backend API. Multi-value selections remain client-side. Backend now validates these filter parameters, and frontend wiring determines when to re-fetch versus re-render.

Changes

Service/Region Filter Pushdown

Layer / File(s) Summary
Backend API pushdown validation
internal/api/handler_recommendations_test.go
Test verifies that getRecommendations translates service, region, and account_ids query parameters into the expected RecommendationFilter shape, and validates error responses for invalid parameter values.
Frontend API filter construction
frontend/src/recommendations.ts
loadRecommendations() derives pushedService and pushedRegion from categorical column filters and includes them in API parameters only when each has exactly one value; documentation updated to reflect server-side WHERE pruning.
Frontend filter commit and refresh orchestration
frontend/src/recommendations.ts
Introduced SERVER_PUSHDOWN_COLUMNS constant and commitAndRefresh helper that routes categorical filter commits to loadRecommendations() (async re-fetch) for service/region/account, or rerenderRecommendations() (local re-render) for other filters; clear handlers updated to use this path.
Frontend test coverage for pushdown behavior
frontend/src/__tests__/recommendations.test.ts
Added tests verifying single-value service and region pushdown to API, multi-value non-pushdown, and no-filter baseline; fixed mock return value handling to account for multiple column-filter reads during render.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • LeanerCloud/CUDly#162: This PR directly implements server-side pushdown for single-value Service and Region filters, fulfilling the requirement to add this filtering optimization.

Possibly related PRs

  • LeanerCloud/CUDly#160: Explicitly removes legacy backend service/region plumbing and expects only provider/account_ids pushdown, directly conflicting with this PR's restoration of service/region server-side filtering.

  • LeanerCloud/CUDly#181: Modifies the frontend service column-filter behavior in recommendations.ts and impacts how the service filter commit path works, directly affected by this PR's new pushdown/refresh orchestration.

Poem

🐰 Hop forward, the filters now push downstream,
Service and region flee to the backend's dream,
Single values race ahead with the API call,
While multi-value picks dance client-side through it all,
A fleet-footed fetch, a refresh so keen—
The cleanest filter push we've ever seen! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing server-side pushdown filtering for service/region/account filters in the recommendations feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/162-wave14

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

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@frontend/src/__tests__/recommendations.test.ts`:
- Around line 172-224: The tests set state.getRecommendationsColumnFilters via
mockReturnValue but never restore it, causing mock leakage; add an afterEach
cleanup in this test file that resets the mock implementation after each test
(e.g., call (state.getRecommendationsColumnFilters as jest.Mock).mockReset() or
mockRestore() in an afterEach block), so loadRecommendations/tests run with a
clean getRecommendationsColumnFilters mock between tests.
🪄 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: 21ee43c2-8bf0-4d98-9ac2-8930f3cc2793

📥 Commits

Reviewing files that changed from the base of the PR and between 4956d66 and 90aad46.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/recommendations.ts
  • internal/api/handler_recommendations_test.go

Comment thread frontend/src/__tests__/recommendations.test.ts
@cristim

cristim commented May 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Rate Limit Exceeded

@cristim have exceeded the limit for the number of chat messages per hour. Please wait 39 minutes and 47 seconds before sending another message.

cristim commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the full PR diff
gh pr view 846 --json files,additions,deletions,title,body

echo "=== PR DIFF ==="
gh pr diff 846

Length of output: 17184


🏁 Script executed:

#!/bin/bash
echo "=== handler_recommendations.go ==="
cat internal/api/handler_recommendations.go

echo ""
echo "=== buildRecommendationFilter or filter builder ==="
fd -e go | xargs grep -l "buildRecommendationFilter" 2>/dev/null

echo ""
echo "=== config.RecommendationFilter struct ==="
rg -n "RecommendationFilter" --type go -C3

echo ""
echo "=== frontend api.ts RecommendationFilters ==="
fd "api.ts" frontend/src | head -5
cat frontend/src/api.ts 2>/dev/null || fd "api.ts" --type f | head -2 | xargs cat

echo ""
echo "=== frontend state.ts relevant parts ==="
rg -n "getRecommendationsColumnFilters\|RecommendationsColumnId\|ColumnFilter" frontend/src/state.ts -C2

echo ""
echo "=== IsClientError ==="
rg -n "IsClientError\|ClientError" --type go -C3

Length of output: 58487


🏁 Script executed:

#!/bin/bash
echo "=== validation.go ==="
fd "validation.go" --type f | head -3 | xargs cat

echo ""
echo "=== ClientError / IsClientError ==="
fd -e go | xargs grep -l "ClientError" 2>/dev/null | head -5
fd "errors.go" --type f internal/api/ | xargs cat 2>/dev/null
rg -n "ClientError\|NewClientError\|IsClientError" --type go -l | head -8 | xargs cat 2>/dev/null | head -80

echo ""
echo "=== frontend api/index.ts ==="
cat frontend/src/api/index.ts 2>/dev/null | head -20

echo ""
echo "=== frontend api RecommendationFilters ==="
fd "*.ts" frontend/src/api/ | xargs grep -l "RecommendationFilter\|getRecommendations" 2>/dev/null | head -5 | xargs cat 2>/dev/null | head -100

echo ""
echo "=== state.ts ColumnFilter / RecommendationsColumnId ==="
fd "state.ts" frontend/src/ | head -3 | xargs grep -n "ColumnFilter\|RecommendationsColumnId\|getRecommendationsColumnFilters" 2>/dev/null

echo ""
echo "=== store_postgres_recommendations.go buildRecommendationFilter ==="
cat internal/config/store_postgres_recommendations.go | grep -A 60 "func buildRecommendationFilter"

Length of output: 47214



@cristim Here is the full review of PR #846.


Full Review — feat(recommendations): server-side pushdown for service/region/account filters

Summary

The overall approach is sound: single-value categorical filters for service and region are extracted in loadRecommendations() and forwarded to the backend as query params, while multi-value or absent filters fall back to applyColumnFilters. The new commitAndRefresh() helper cleanly separates the re-fetch path from the re-render path. The Go side has proper validation (validateServiceName / validateRegion), parameterized SQL, and the new TestGetRecommendations_PushdownFilters sub-tests cover the happy paths and 400 paths well. A few findings follow.


Medium

Missing unit test for commitAndRefresh routing behaviour

commitAndRefresh() is the new orchestration point that routes SERVER_PUSHDOWN_COLUMNS to loadRecommendations() and everything else to rerenderRecommendations(). The existing TS tests validate the downstream effect (API params received), but there is no test that directly asserts:

  • commitAndRefresh('service') → loadRecommendations() called, rerenderRecommendations() not called
  • commitAndRefresh('on_demand_monthly') → rerenderRecommendations() called, loadRecommendations() not called

If SERVER_PUSHDOWN_COLUMNS ever gets a wrong member (e.g., a typo introducing a numeric column), the routing test would catch it where the current suite wouldn't.


Low

account in SERVER_PUSHDOWN_COLUMNS — re-fetch semantics are implicit

commitAndRefresh('account') fires loadRecommendations(), which reads state.getCurrentAccountIDs() (not columnFilters['account']). The intent stated in the comment is:

'account' drives account_ids which is already a server-side hint; include it here so clearing the account filter triggers a re-fetch like the others.

This is only correct if changing the column filter for account also updates the state that getCurrentAccountIDs() reads. If those two are ever out of sync (e.g., the column filter narrows to "acct-A" but getCurrentAccountIDs() still returns all IDs), the re-fetch returns unfiltered data and the column filter silently degrades to client-side only — with an extra API round-trip attached. A test or a one-line assertion in commitAndRefresh for the account case would make this coupling explicit.


getRecommendationsColumnFilters() called twice per loadRecommendations() invocation

loadRecommendations() now calls state.getRecommendationsColumnFilters() once at the top for pushdown extraction, and applyColumnFilters (called later in the same function) calls it again. Each call returns a shallow copy ({ ...recommendationsColumnFilters }). This is not a correctness issue today, but passing the already-extracted columnFilters object down to applyColumnFilters would remove the redundant copy and make it impossible for the two reads to diverge if filter state ever becomes async in a future refactor.


Nits

Region SQL filter has no isolation test in store_postgres_recommendations_test.go

store_postgres_recommendations_test.go (around line 172) tests Service and Provider filter isolation against a real Postgres instance, but there is no analogous test for Region:

// Existing
got, err = store.ListStoredRecommendations(ctx, config.RecommendationFilter{Service: "ec2"})
// Missing
got, err = store.ListStoredRecommendations(ctx, config.RecommendationFilter{Region: "us-east-1"})

buildRecommendationFilter handles Region identically to Service, so the risk is low, but the coverage gap exists.


validateServiceName has a redundant length guard

// internal/api/validation.go
if len(service) > 64 {
    return NewClientError(400, "invalid service name: must be 1-64 characters")
}
if !serviceNameRegex.MatchString(service) { // ^[a-z0-9][a-z0-9-]{0,63}$
    ...
}

The regex already caps total length at 64 ([a-z0-9-]{0,63} after the first char). The explicit len > 64 check is dead code that fires only for strings that would also fail the regex. Minor, and pre-existing — but if you are touching this file, it is worth collapsing to just the regex check.


Positive notes

  • The mockReturnValueOnce → mockReturnValue + explicit reset for the on_demand_monthly test is the correct fix; the in-code comment explaining why jest.clearAllMocks isn't sufficient here is helpful.
  • The TestGetRecommendations_PushdownFilters sub-tests use strict mock expectations (AssertExpectations) and cover both the happy path and both 400 rejection paths — good signal fidelity.
  • The "multi-value service filter stays client-side" TS test closes the loop on the most important boundary condition for the pushdown decision.

🧠 Learnings used
Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-05-05T15:44:54.463Z
Learning: In the LeanerCloud/CUDly repository, when a CodeRabbit review flags something as "outside-diff" that was already implemented in an earlier commit of the same PR, it is likely a stale-snapshot artifact where the review diff was generated against a pre-fix view. The inline comment convention `(CR pass on PR #<number> Finding N)` is used by the maintainer (cristim) to mark lines that were changed in direct response to a prior CodeRabbit review pass.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:28:28.139Z
Learning: In LeanerCloud/CUDly `providers/gcp/recommendations.go`, `collectRegion` fans out to all four GCP service clients (computeengine, cloudsql, memorystore, cloudstorage) concurrently (fixed in PR `#1047` commit 95a390f, H-2 finding). `regionResult` gains `cache` and `storage` slices; merge is updated accordingly. The decision was to wire (not trim) memorystore and cloudstorage because both have complete `GetRecommendations` implementations and their no-op `PurchaseCommitment` paths are orthogonal to surfacing advisory recommendations. Regression tests: `TestRegionResult_HasCacheAndStorageFields` and `TestShouldIncludeService_Cache_Storage`.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:28:28.139Z
Learning: In LeanerCloud/CUDly GCP service clients (computeengine, cloudsql, memorystore, cloudstorage), `GetRecommendations` filters out non-ACTIVE recommendations using the typed `recommenderpb.RecommendationStateInfo_ACTIVE` enum (CLAIMED/SUCCEEDED/FAILED/DISMISSED are skipped). State is accessed nil-safely via the proto getter. Fixed in PR `#1047` commit 95a390f (H-1 finding). Regression tests: `TestGetRecommendations_FiltersNonActiveStates` (5-rec mock, only ACTIVE survives) and `TestGetRecommendations_ActiveRecIncluded`. Existing mock tests in all four services had StateInfo added to satisfy the new filter.

@cristim

cristim commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@cristim
cristim changed the base branch from feat/multicloud-web-frontend to main June 9, 2026 15:44
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

cristim added 2 commits July 17, 2026 21:11
…t filters (closes #162)

Wire service and region as SQL WHERE-clause parameters in ListRecommendations
so the backend prunes rows before returning them. The frontend extracts a
single-value categorical selection from the column-filter state and forwards
it as a query param; multi-value or absent filters fall through to client-side
applyColumnFilters unchanged. Account filter already triggered re-fetches via
account_ids; add it to SERVER_PUSHDOWN_COLUMNS so clearing it also re-fetches.

Backend: handler_recommendations.go already called validateServiceName /
validateRegion / buildRecommendationFilter -- no Go changes needed beyond
the test coverage added here.

Frontend: commitAndRefresh() replaces the direct rerenderRecommendations()
call in categorical popover commits; it re-fetches for the three pushdown
columns and re-renders in-place for all others.

Tests added:
- Go: TestGetRecommendations_PushdownFilters (4 sub-tests covering
  service+region reach filter, account_id reaches filter, invalid service
  returns 400, invalid region returns 400)
- TS: 3 new loadRecommendations tests (single-value service pushed, single-
  value region pushed, multi-value service not pushed)
@cristim
cristim merged commit f7c88ba into main Jul 19, 2026
20 checks passed
@cristim
cristim deleted the fix/162-wave14 branch July 27, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/feat New capability urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant