Skip to content

feat: push OAuth2 sessions filtering and pagination to SQL with total count - #4775

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
06-29-feat_adds_pagination_to_mcp_oauth_grants_table
Jun 30, 2026
Merged

feat: push OAuth2 sessions filtering and pagination to SQL with total count#4775
Pratham-Mishra04 merged 1 commit into
devfrom
06-29-feat_adds_pagination_to_mcp_oauth_grants_table

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

The OAuth Grants (Connected Clients) UI previously loaded all active sessions in a single query and performed filtering and pagination entirely in the browser. This PR moves filtering (case-insensitive search across client name, client ID, bound identity, and virtual key display name; bf_mode filter) and pagination (limit/offset) into SQL, and adds a total-count return value so the UI can render accurate page indicators without holding the full dataset in memory.

Changes

  • ListOAuth2Sessions now accepts an OAuth2SessionsQueryParams struct (search, modes, limit, offset) and returns a second int64 total-count value alongside the page slice. Filtering and pagination are applied in SQL using a shared base query; a gorm.Session fork ensures the count and the page query don't pollute each other's statement.
  • GET /api/oauth2/sessions parses q, bf_mode, limit, and offset query parameters, validates them, and forwards them to the store. The response envelope now includes count, total_count, limit, and offset fields, matching the shape used by the MCP auth-sessions endpoint.
  • The UI switches from local useState for search/mode/offset to nuqs URL query state, so filter and page selections survive navigation and can be bookmarked. Search is debounced (300 ms) before triggering a fetch. RTK Query receives the filter+page params directly, giving each combination its own cache entry.
  • The grants table gains a sticky header and a scrollable body so the page layout no longer grows unboundedly. The pagination bar is always visible when there are results (not only when results exceed one page), and shows an entry range and page-of-total indicator.
  • A new TestListOAuth2Sessions_FilterAndPaginate test pins ordering (created_at DESC), limit/offset paging, total-count independence from the page slice, bf_mode filtering, and case-insensitive search against the joined client name, joined VK display name, and bound identity.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

# Core/Transports
go test ./framework/configstore/... -run TestListOAuth2Sessions
go test ./transports/bifrost-http/...

# UI
cd ui
pnpm i
pnpm build

Navigate to the OAuth Grants page, enter a search term, toggle a mode filter, and verify the URL updates and results narrow correctly. Advance to a second page and confirm the total count and page indicator remain accurate. Revoke the last grant on a non-first page and confirm the offset snaps back rather than leaving a blank page.

Screenshots/Recordings

Add before/after screenshots of the grants table and pagination bar.

Breaking changes

  • Yes
  • No

ListOAuth2Sessions signature changed: callers must pass an OAuth2SessionsQueryParams argument and accept a second int64 return value. Any mock or alternative implementation of ConfigStore must be updated accordingly (the in-tree mocks in mcpoauth2jwt_test.go and lib/config_test.go have been updated).

Related issues

Security considerations

Search terms are passed to SQL as LIKE parameters via GORM's parameterised query API; no raw interpolation is performed. The ScopedDB wrapper is preserved, so row-visibility predicates injected via context continue to apply to both the count and the page query.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: 4 seconds

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 31d68085-8b6b-42b0-9966-1893d8b023f1

📥 Commits

Reviewing files that changed from the base of the PR and between 4a49cd9 and 0441eb4.

📒 Files selected for processing (3)
  • framework/configstore/rdb.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/configstore/store.go
📝 Walkthrough

Walkthrough

Adds server-side search, mode filtering, and offset-based pagination to the OAuth2 sessions listing. The ConfigStore interface gains OAuth2SessionsQueryParams; the RDB implementation builds a filtered base query and returns a total count; the HTTP handler parses and validates query params; the RTK Query endpoint is parameterized; and the UI migrates from local state to URL-backed nuqs query state.

Changes

OAuth2 Sessions Filter & Pagination

Layer / File(s) Summary
OAuth2SessionsQueryParams contract
framework/configstore/store.go
Adds OAuth2SessionsQueryParams struct (Search, Modes, Limit, Offset) and updates ConfigStore.ListOAuth2Sessions to accept it and return ([]OAuth2SessionRow, int64, error).
RDB implementation: filtered query, count, and paged fetch
framework/configstore/rdb.go, framework/configstore/rdb_oauth2_test.go
Rewrites ListOAuth2Sessions to build a joined/filtered base query, compute totalCount via Count(), then fetch the requested page with Limit/Offset. Tests cover revoked exclusion, ordering, pagination, mode filtering, and search.
HTTP handler: query parsing and new response shape
transports/bifrost-http/handlers/mcpoauth2sessions.go, transports/bifrost-http/handlers/mcpoauth2jwt_test.go, transports/bifrost-http/lib/config_test.go
Adds parseOAuth2SessionsListQuery to extract/validate q, bf_mode, limit, offset; wires listSessions to call ListOAuth2Sessions with params; emits oauth2SessionsListResponse with count/total_count/limit/offset. Mock impls updated accordingly.
RTK Query API: parameterized endpoint
ui/lib/store/apis/oauth2SessionsApi.ts
Adds OAuth2GrantsQueryParams, buildOAuth2GrantsListParams, extends OAuth2GrantsListResponse with pagination fields, and updates getOAuth2Grants to pass params to /oauth2/sessions.
OAuthGrantsPage and GrantsTable: URL-backed state
ui/app/workspace/oauth-grants/page.tsx, ui/app/workspace/oauth-grants/views/grantsTable.tsx
Replaces local React state with nuqs URL-backed query state; adds offset snap-into-range effect; rewires filter bar, table, and pagination to read/write URL state; updates pagination markup to show "X–Y of Z entries" and "Page N of M".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop, hop! The sessions now page with flair,
URL state keeps the filters right there.
A count returns with every query made,
No more slicing client-side, I'm afraid.
The rabbit cheers: server-side wins the race! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Title check ✅ Passed The title clearly summarizes the main change: moving OAuth2 session filtering and pagination to SQL with a total count.
Description check ✅ Passed The description covers the required sections well, including summary, changes, testing, breaking changes, security, and checklist.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-29-feat_adds_pagination_to_mcp_oauth_grants_table

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

Pratham-Mishra04 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
framework/configstore/rdb.go Adds filtered and paginated SQL listing for active OAuth2 sessions with a separate total count.
transports/bifrost-http/handlers/mcpoauth2sessions.go Parses list query parameters and returns sessions with pagination metadata.
ui/app/workspace/oauth-grants/page.tsx Uses URL query state and server query params for OAuth grants filters and paging.
ui/app/workspace/oauth-grants/views/grantsTable.tsx Updates the grants table layout and keeps pagination controls visible when results exist.
ui/lib/store/apis/oauth2SessionsApi.ts Adds typed filter and pagination params for the OAuth grants API request.

Reviews (7): Last reviewed commit: "feat: adds pagination to mcp oauth grant..." | Re-trigger Greptile

@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: 2

🧹 Nitpick comments (2)
ui/lib/store/apis/oauth2SessionsApi.ts (1)

34-48: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Canonicalize bf_mode before it becomes the RTK Query cache key.

Sorting in buildOAuth2GrantsListParams only affects the outgoing request params; RTK Query’s cache entry is derived from the original query arg, so ["user", "vk"] and ["vk", "user"] can still fragment the cache. Sort the array before calling useGetOAuth2GrantsQuery, or add an endpoint-level serializeQueryArgs.

🤖 Prompt for 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.

In `@ui/lib/store/apis/oauth2SessionsApi.ts` around lines 34 - 48, Canonicalize
bf_mode in the query arg used by oauth2SessionsApi so RTK Query sees a stable
cache key; sorting only inside buildOAuth2GrantsListParams does not affect the
key derived from getOAuth2Grants. Update the caller path before
useGetOAuth2GrantsQuery or add serializeQueryArgs on the getOAuth2Grants
endpoint so arrays like ["user","vk"] and ["vk","user"] map to the same cache
entry.
ui/app/workspace/oauth-grants/page.tsx (1)

45-50: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Depend on a loaded boolean instead of the response object.

data is only a readiness guard here; depending on the full response object can rerun this effect on each refetch/poll. Use a derived boolean such as const hasLoadedPage = data !== undefined and depend on that with totalCount and urlState.offset.

Based on learnings: “In paginated views, omit the API response object ... if it is only used as a null-guard and meaningful changes surface through derived primitives.”

🤖 Prompt for 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.

In `@ui/app/workspace/oauth-grants/page.tsx` around lines 45 - 50, The effect in
oauth-grants/page.tsx is using the full response object as a readiness guard,
which can cause reruns on every refetch even when the meaningful state has not
changed. Replace the direct dependency on data in the useEffect that updates
setUrlState with a derived boolean like hasLoadedPage = data !== undefined, and
use that boolean alongside totalCount and urlState.offset in the dependency
list. Keep the existing pagination adjustment logic intact, but ensure the
effect only reacts to loaded-state transitions and the primitive values it
actually uses.

Source: Learnings

🤖 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 `@framework/configstore/rdb.go`:
- Around line 7104-7111: The pagination query in the grant list is only ordered
by rt.created_at DESC, which can shuffle rows with identical timestamps between
pages. Update the query built in this block to add a unique secondary sort key
after created_at, using the same query chain that applies Limit and Offset so
the ordering stays stable. Also add or adjust a test around the pagination path
to verify rows with the same timestamp stay in a deterministic order across
pages.

In `@transports/bifrost-http/handlers/mcpoauth2sessions.go`:
- Around line 66-67: The mcpoauth2sessions handler currently accepts any bf_mode
value via parseCommaSeparated, which lets unsupported modes slip through
silently. In the request handling logic around q.Modes, validate the bf_mode
query values against the documented whitelist of user, vk, and session before
calling the store, and return a 400 for any invalid value instead of passing
arbitrary strings onward.

---

Nitpick comments:
In `@ui/app/workspace/oauth-grants/page.tsx`:
- Around line 45-50: The effect in oauth-grants/page.tsx is using the full
response object as a readiness guard, which can cause reruns on every refetch
even when the meaningful state has not changed. Replace the direct dependency on
data in the useEffect that updates setUrlState with a derived boolean like
hasLoadedPage = data !== undefined, and use that boolean alongside totalCount
and urlState.offset in the dependency list. Keep the existing pagination
adjustment logic intact, but ensure the effect only reacts to loaded-state
transitions and the primitive values it actually uses.

In `@ui/lib/store/apis/oauth2SessionsApi.ts`:
- Around line 34-48: Canonicalize bf_mode in the query arg used by
oauth2SessionsApi so RTK Query sees a stable cache key; sorting only inside
buildOAuth2GrantsListParams does not affect the key derived from
getOAuth2Grants. Update the caller path before useGetOAuth2GrantsQuery or add
serializeQueryArgs on the getOAuth2Grants endpoint so arrays like ["user","vk"]
and ["vk","user"] map to the same cache entry.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a91484d5-eddf-4187-ae24-30acb6529e44

📥 Commits

Reviewing files that changed from the base of the PR and between eae8b64 and 4a49cd9.

📒 Files selected for processing (9)
  • framework/configstore/rdb.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/configstore/store.go
  • transports/bifrost-http/handlers/mcpoauth2jwt_test.go
  • transports/bifrost-http/handlers/mcpoauth2sessions.go
  • transports/bifrost-http/lib/config_test.go
  • ui/app/workspace/oauth-grants/page.tsx
  • ui/app/workspace/oauth-grants/views/grantsTable.tsx
  • ui/lib/store/apis/oauth2SessionsApi.ts

Comment thread framework/configstore/rdb.go
Comment thread transports/bifrost-http/handlers/mcpoauth2sessions.go
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-feat_adds_pagination_to_mcp_oauth_grants_table branch from 4a49cd9 to df6e27c Compare June 30, 2026 07:53
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-fix_mcp_sessions_ui_fixes branch 2 times, most recently from 3d10adc to e3dc25b Compare June 30, 2026 07:56
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-feat_adds_pagination_to_mcp_oauth_grants_table branch from df6e27c to 6ea5663 Compare June 30, 2026 07:56
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 30, 2026

Pratham-Mishra04 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Jun 30, 1:53 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 30, 2:44 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 30, 2:45 PM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from 06-29-fix_mcp_sessions_ui_fixes to graphite-base/4775 June 30, 2026 14:40
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/4775 to dev June 30, 2026 14:43
@Pratham-Mishra04
Pratham-Mishra04 dismissed coderabbitai[bot]’s stale review June 30, 2026 14:43

The base branch was changed.

@Pratham-Mishra04
Pratham-Mishra04 requested a review from a team as a code owner June 30, 2026 14:43
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 06-29-feat_adds_pagination_to_mcp_oauth_grants_table branch from 375e4e3 to 0441eb4 Compare June 30, 2026 14:43
@Pratham-Mishra04
Pratham-Mishra04 merged commit cdbeeba into dev Jun 30, 2026
14 of 16 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 06-29-feat_adds_pagination_to_mcp_oauth_grants_table branch June 30, 2026 14:45
akshaydeo pushed a commit that referenced this pull request Jul 1, 2026
… count (#4775)

## Summary

The OAuth Grants (Connected Clients) UI previously loaded all active sessions in a single query and performed filtering and pagination entirely in the browser. This PR moves filtering (case-insensitive search across client name, client ID, bound identity, and virtual key display name; `bf_mode` filter) and pagination (limit/offset) into SQL, and adds a total-count return value so the UI can render accurate page indicators without holding the full dataset in memory.

## Changes

- `ListOAuth2Sessions` now accepts an `OAuth2SessionsQueryParams` struct (search, modes, limit, offset) and returns a second `int64` total-count value alongside the page slice. Filtering and pagination are applied in SQL using a shared base query; a `gorm.Session` fork ensures the count and the page query don't pollute each other's statement.
- `GET /api/oauth2/sessions` parses `q`, `bf_mode`, `limit`, and `offset` query parameters, validates them, and forwards them to the store. The response envelope now includes `count`, `total_count`, `limit`, and `offset` fields, matching the shape used by the MCP auth-sessions endpoint.
- The UI switches from local `useState` for search/mode/offset to `nuqs` URL query state, so filter and page selections survive navigation and can be bookmarked. Search is debounced (300 ms) before triggering a fetch. RTK Query receives the filter+page params directly, giving each combination its own cache entry.
- The grants table gains a sticky header and a scrollable body so the page layout no longer grows unboundedly. The pagination bar is always visible when there are results (not only when results exceed one page), and shows an entry range and page-of-total indicator.
- A new `TestListOAuth2Sessions_FilterAndPaginate` test pins ordering (created_at DESC), limit/offset paging, total-count independence from the page slice, `bf_mode` filtering, and case-insensitive search against the joined client name, joined VK display name, and bound identity.

## Type of change

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [x] UI (React)
- [ ] Docs

## How to test

```sh
# Core/Transports
go test ./framework/configstore/... -run TestListOAuth2Sessions
go test ./transports/bifrost-http/...

# UI
cd ui
pnpm i
pnpm build
```

Navigate to the OAuth Grants page, enter a search term, toggle a mode filter, and verify the URL updates and results narrow correctly. Advance to a second page and confirm the total count and page indicator remain accurate. Revoke the last grant on a non-first page and confirm the offset snaps back rather than leaving a blank page.

## Screenshots/Recordings

_Add before/after screenshots of the grants table and pagination bar._

## Breaking changes

- [x] Yes
- [ ] No

`ListOAuth2Sessions` signature changed: callers must pass an `OAuth2SessionsQueryParams` argument and accept a second `int64` return value. Any mock or alternative implementation of `ConfigStore` must be updated accordingly (the in-tree mocks in `mcpoauth2jwt_test.go` and `lib/config_test.go` have been updated).

## Related issues

## Security considerations

Search terms are passed to SQL as `LIKE` parameters via GORM's parameterised query API; no raw interpolation is performed. The `ScopedDB` wrapper is preserved, so row-visibility predicates injected via context continue to apply to both the count and the page query.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
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