Skip to content

feat: add server-side pagination to operation overrides with limit and offset parameters#2695

Merged
JivusAyrus merged 5 commits intomainfrom
suvij/eng-9297-studio-add-server-side-pagination-to-operation-overrides
Mar 26, 2026
Merged

feat: add server-side pagination to operation overrides with limit and offset parameters#2695
JivusAyrus merged 5 commits intomainfrom
suvij/eng-9297-studio-add-server-side-pagination-to-operation-overrides

Conversation

@JivusAyrus
Copy link
Copy Markdown
Member

@JivusAyrus JivusAyrus commented Mar 26, 2026

Summary by CodeRabbit

  • New Features
    • Server-side pagination for operation overrides: page-size and page-navigation controls, manual pagination mode, display of total overrides, and improved empty-state when none exist.
  • Tests
    • Added tests validating limit/offset pagination behavior and total-count consistency across multiple scenarios.

Checklist

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0b37aed3-ddb3-451b-bc21-d1ac23d38a5e

📥 Commits

Reviewing files that changed from the base of the PR and between b3b9adf and a986d3a.

📒 Files selected for processing (1)
  • controlplane/test/override.test.ts
✅ Files skipped from review due to trivial changes (1)
  • controlplane/test/override.test.ts

Walkthrough

Added request pagination (limit, offset) and response total_count to GetAllOverrides; server normalizes pagination and forwards it to the repository, repository returns paginated rows plus total count; tests added; frontend switched to manual pagination using totalCount.

Changes

Cohort / File(s) Summary
Proto / Generated Types
proto/wg/cosmo/platform/v1/platform.proto, connect/src/wg/cosmo/platform/v1/platform_pb.ts
Added int32 limit = 3 and int32 offset = 4 to GetAllOverridesRequest; added int32 total_count = 3 to GetAllOverridesResponse. Regenerated TS bindings include new fields and metadata.
Service / Endpoint
controlplane/src/core/bufservices/check/getAllOverrides.ts
Normalize pagination via normalizePagination, pass limit/offset to repository call, and include totalCount in the handler response (0 when graph not found).
Repository / Data Layer
controlplane/src/core/repositories/OperationsRepository.ts
getConsolidatedOverridesView signature changed to accept { namespaceId, limit, offset }, made async, runs paginated base query and separate count query in parallel, returns { overrides, totalCount }.
Pagination Utilities & Constants
controlplane/src/core/util.ts, controlplane/src/core/constants.ts
Added normalizePagination and paginationDefaults; normalization applies defaults and clamps limit/offset, with optional overrides for max values.
Tests
controlplane/src/core/util.test.ts, controlplane/test/override.test.ts
Added unit tests for normalizePagination and an integration Vitest that verifies pagination behavior and totalCount across various limit/offset scenarios.
Frontend / UI
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/overrides.tsx
Switched table to manual pagination: derive page state from router query, pass limit/offset to getAllOverrides, compute pages from data.totalCount, update empty-state check and caption, and add Pagination component.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding server-side pagination to operation overrides with limit and offset parameters, which is the core objective of this PR.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 73.58491% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.10%. Comparing base (4416030) to head (b74234c).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
[...zationSlug]/[namespace]/graph/[slug]/overrides.tsx](https://app.codecov.io/gh/wundergraph/cosmo/pull/2695?src=pr&el=tree&filepath=studio%2Fsrc%2Fpages%2F%5BorganizationSlug%5D%2F%5Bnamespace%5D%2Fgraph%2F%5Bslug%5D%2Foverrides.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wundergraph#diff-c3R1ZGlvL3NyYy9wYWdlcy9bb3JnYW5pemF0aW9uU2x1Z10vW25hbWVzcGFjZV0vZ3JhcGgvW3NsdWddL292ZXJyaWRlcy50c3g=) 0.00% 13 Missing ⚠️
...lane/src/core/bufservices/check/getAllOverrides.ts 83.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2695       +/-   ##
===========================================
- Coverage   63.09%   40.10%   -22.99%     
===========================================
  Files         245      975      +730     
  Lines       26273   122862    +96589     
  Branches        0     5535     +5535     
===========================================
+ Hits        16576    49275    +32699     
- Misses       8354    71935    +63581     
- Partials     1343     1652      +309     
Files with missing lines Coverage Δ
controlplane/src/core/constants.ts 100.00% <100.00%> (ø)
...lane/src/core/repositories/OperationsRepository.ts 84.66% <100.00%> (ø)
controlplane/src/core/util.ts 81.23% <100.00%> (ø)
...lane/src/core/bufservices/check/getAllOverrides.ts 73.17% <83.33%> (ø)
[...zationSlug]/[namespace]/graph/[slug]/overrides.tsx](https://app.codecov.io/gh/wundergraph/cosmo/pull/2695?src=pr&el=tree&filepath=studio%2Fsrc%2Fpages%2F%5BorganizationSlug%5D%2F%5Bnamespace%5D%2Fgraph%2F%5Bslug%5D%2Foverrides.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wundergraph#diff-c3R1ZGlvL3NyYy9wYWdlcy9bb3JnYW5pemF0aW9uU2x1Z10vW25hbWVzcGFjZV0vZ3JhcGgvW3NsdWddL292ZXJyaWRlcy50c3g=) 0.00% <0.00%> (ø)

... and 727 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 26, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-36940359f183e92d0babf39d34440dc16eb6fcae

Comment thread controlplane/src/core/bufservices/check/getAllOverrides.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/overrides.tsx (1)

53-55: Minor: Inconsistent parseInt usage.

Line 53 uses parseInt() while line 54 uses Number.parseInt(). Both work identically, but consider using a consistent style throughout the file.

♻️ Suggested consistency fix
-  const pageNumber = router.query.page ? parseInt(router.query.page as string, 10) : 1;
-  const pageSize = Number.parseInt((router.query.pageSize as string) || '10');
+  const pageNumber = router.query.page ? Number.parseInt(router.query.page as string, 10) : 1;
+  const pageSize = Number.parseInt((router.query.pageSize as string) || '10', 10);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@studio/src/pages/`[organizationSlug]/[namespace]/graph/[slug]/overrides.tsx
around lines 53 - 55, The file uses parseInt in pageNumber and Number.parseInt
in pageSize; make them consistent by using the same parse function everywhere
(e.g., replace Number.parseInt((router.query.pageSize as string) || '10') with
parseInt((router.query.pageSize as string) || '10', 10) or switch pageNumber to
Number.parseInt), ensuring both calls include the radix and reference the same
router.query keys (pageNumber, pageSize).
controlplane/test/override.test.ts (1)

244-246: Annotate testContext explicitly.

The new callback adds an inferred parameter type. Please type it as TestContext so this stays aligned with the project’s TypeScript rule.

Possible change
-import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi, Mock } from 'vitest';
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi, Mock, TestContext } from 'vitest';
...
-  test('Should paginate overrides with limit and offset', async (testContext) => {
+  test('Should paginate overrides with limit and offset', async (testContext: TestContext) => {

As per coding guidelines, "Use explicit type annotations for function parameters and return types in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/test/override.test.ts` around lines 244 - 246, The test
callback's parameter is currently untyped (async (testContext) => ...); change
it to an explicit type by annotating the parameter as TestContext (async
(testContext: TestContext) => ...) in the 'Should paginate overrides with limit
and offset' test, and add or ensure the appropriate TestContext import is
present (e.g., import type { TestContext } from 'ava' or your test framework) so
the parameter type is explicit and satisfies the TypeScript rule.
controlplane/src/core/repositories/OperationsRepository.ts (2)

511-515: Keep the page rows and totalCount on the same snapshot.

Lines 511-515 execute two independent selects. If an override is added or removed between them, the returned overrides and totalCount can disagree for the same response. If that pair is expected to be internally consistent, run them in a transaction or fold the count into the paged query.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` around lines 511
- 515, The overrides and totalCount are fetched by two independent queries
(baseQuery and countQuery) which can yield inconsistent snapshots; in
OperationsRepository make them consistent by executing both inside a single
transaction or by folding the count into the paged query (e.g. use COUNT(*)
OVER() or a single query that returns rows plus total) so that the returned
overrides and totalCount are from the same snapshot; update the code that
currently awaits Promise.all([baseQuery, countQuery]) and then returns {
overrides, totalCount } to instead run the two operations in a transaction or
replace them with a single paged query that produces both the rows and the
totalCount.

446-446: Give this public method an explicit return contract.

getConsolidatedOverridesView now returns a paginated payload, but its shape is still inferred from the Drizzle query. A named interface plus an explicit Promise<...> return type will keep SQL inference from leaking into callers.

Possible change
+interface ConsolidatedOverrideRow {
+  hash: string;
+  name: string;
+  updatedAt: string;
+  hasIgnoreAllOverride: boolean;
+  changesOverrideCount: number;
+}
+
+interface ConsolidatedOverridesView {
+  overrides: ConsolidatedOverrideRow[];
+  totalCount: number;
+}
...
-  public async getConsolidatedOverridesView(data: { namespaceId: string; limit: number; offset: number }) {
+  public async getConsolidatedOverridesView(
+    data: { namespaceId: string; limit: number; offset: number },
+  ): Promise<ConsolidatedOverridesView> {

As per coding guidelines, "Prefer interfaces over type aliases for object shapes in TypeScript" and "Use explicit type annotations for function parameters and return types in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` at line 446, The
public method getConsolidatedOverridesView should declare an explicit interface
for the paginated payload and use it as the function's return type to avoid
leaking Drizzle SQL inference; create a named interface (e.g.,
ConsolidatedOverridesView and ConsolidatedOverridesViewPage) describing the
object shape (items array and pagination metadata like limit/offset/total) and
update the method signature to public async getConsolidatedOverridesView(data: {
namespaceId: string; limit: number; offset: number }):
Promise<ConsolidatedOverridesViewPage> so callers consume a stable, documented
contract instead of an inferred type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@controlplane/test/override.test.ts`:
- Around line 283-310: The pagination test currently only checks totalCount and
lengths—add explicit assertions that the returned override identities/hashes
match expected items to ensure offset/limit are applied correctly: after calling
client.getAllOverrides (variables allRes, page1, page2) assert the arrays of
override hashes (e.g. map each .overrides to .hash or unique id) equal the
expected ordered arrays (allRes hashes contain all three in insertion order,
page1 hashes equal the first two, page2 hashes equal the third) so handlers that
ignore offset/limit or apply defaults will fail.

---

Nitpick comments:
In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Around line 511-515: The overrides and totalCount are fetched by two
independent queries (baseQuery and countQuery) which can yield inconsistent
snapshots; in OperationsRepository make them consistent by executing both inside
a single transaction or by folding the count into the paged query (e.g. use
COUNT(*) OVER() or a single query that returns rows plus total) so that the
returned overrides and totalCount are from the same snapshot; update the code
that currently awaits Promise.all([baseQuery, countQuery]) and then returns {
overrides, totalCount } to instead run the two operations in a transaction or
replace them with a single paged query that produces both the rows and the
totalCount.
- Line 446: The public method getConsolidatedOverridesView should declare an
explicit interface for the paginated payload and use it as the function's return
type to avoid leaking Drizzle SQL inference; create a named interface (e.g.,
ConsolidatedOverridesView and ConsolidatedOverridesViewPage) describing the
object shape (items array and pagination metadata like limit/offset/total) and
update the method signature to public async getConsolidatedOverridesView(data: {
namespaceId: string; limit: number; offset: number }):
Promise<ConsolidatedOverridesViewPage> so callers consume a stable, documented
contract instead of an inferred type.

In `@controlplane/test/override.test.ts`:
- Around line 244-246: The test callback's parameter is currently untyped (async
(testContext) => ...); change it to an explicit type by annotating the parameter
as TestContext (async (testContext: TestContext) => ...) in the 'Should paginate
overrides with limit and offset' test, and add or ensure the appropriate
TestContext import is present (e.g., import type { TestContext } from 'ava' or
your test framework) so the parameter type is explicit and satisfies the
TypeScript rule.

In `@studio/src/pages/`[organizationSlug]/[namespace]/graph/[slug]/overrides.tsx:
- Around line 53-55: The file uses parseInt in pageNumber and Number.parseInt in
pageSize; make them consistent by using the same parse function everywhere
(e.g., replace Number.parseInt((router.query.pageSize as string) || '10') with
parseInt((router.query.pageSize as string) || '10', 10) or switch pageNumber to
Number.parseInt), ensuring both calls include the radix and reference the same
router.query keys (pageNumber, pageSize).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0113656e-1410-4438-9073-100a422b0a48

📥 Commits

Reviewing files that changed from the base of the PR and between f9a7513 and ee00b56.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (6)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts
  • controlplane/src/core/bufservices/check/getAllOverrides.ts
  • controlplane/src/core/repositories/OperationsRepository.ts
  • controlplane/test/override.test.ts
  • proto/wg/cosmo/platform/v1/platform.proto
  • studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/overrides.tsx

Comment thread controlplane/test/override.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
controlplane/src/core/repositories/OperationsRepository.ts (2)

446-446: Add explicit return type annotation.

The method is missing an explicit return type annotation. As per coding guidelines, TypeScript functions should have explicit type annotations for return types.

Proposed fix
-  public async getConsolidatedOverridesView(data: { namespaceId: string; limit: number; offset: number }) {
+  public async getConsolidatedOverridesView(data: { namespaceId: string; limit: number; offset: number }): Promise<{
+    overrides: {
+      hash: string;
+      name: string;
+      updatedAt: string;
+      hasIgnoreAllOverride: boolean;
+      changesOverrideCount: number;
+    }[];
+    totalCount: number;
+  }> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` at line 446, The
method getConsolidatedOverridesView is missing an explicit return type; update
its signature to include the correct TypeScript return annotation (e.g., change
"public async getConsolidatedOverridesView(data: { namespaceId: string; limit:
number; offset: number })" to "public async getConsolidatedOverridesView(data: {
namespaceId: string; limit: number; offset: number }): Promise<YourReturnType>"
where YourReturnType matches the resolved value the function returns (use the
existing domain type like ConsolidatedOverridesView[] or an appropriate
interface/Promise<any> if necessary).

505-510: Cast count to int for consistency and type safety.

The existing code on line 495 uses cast(...as int) to ensure numeric types from PostgreSQL aggregate functions. PostgreSQL's COUNT returns bigint, and the postgres-js driver may return this as a string. For consistency with the established pattern and to ensure proper typing for the proto int32 total_count field, apply the same casting approach here.

Proposed fix
     // For pagination, we need the total count of unique operations that have an override. This is obtained by counting the full join of the two tables.
     const countQuery = this.db
       .select({
-        count: count(),
+        count: sql<number>`cast(count(*) as int)`,
       })
       .from(change)
       .fullJoin(ignore, and(eq(change.hash, ignore.hash), eq(change.namespaceId, ignore.namespaceId)));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controlplane/src/core/repositories/OperationsRepository.ts` around lines 505
- 510, The count query in OperationsRepository building `countQuery` uses
count() which yields a bigint/string from Postgres; change the select to cast
the aggregate to int for consistency with the existing pattern (as used earlier
on line ~495). Replace the select object to use cast(count(), 'int') (or the
codebase's cast(... as int) helper) so the selected field remains an integer for
the proto `int32 total_count` (references: OperationsRepository, countQuery,
change, ignore, count()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@controlplane/src/core/repositories/OperationsRepository.ts`:
- Line 446: The method getConsolidatedOverridesView is missing an explicit
return type; update its signature to include the correct TypeScript return
annotation (e.g., change "public async getConsolidatedOverridesView(data: {
namespaceId: string; limit: number; offset: number })" to "public async
getConsolidatedOverridesView(data: { namespaceId: string; limit: number; offset:
number }): Promise<YourReturnType>" where YourReturnType matches the resolved
value the function returns (use the existing domain type like
ConsolidatedOverridesView[] or an appropriate interface/Promise<any> if
necessary).
- Around line 505-510: The count query in OperationsRepository building
`countQuery` uses count() which yields a bigint/string from Postgres; change the
select to cast the aggregate to int for consistency with the existing pattern
(as used earlier on line ~495). Replace the select object to use cast(count(),
'int') (or the codebase's cast(... as int) helper) so the selected field remains
an integer for the proto `int32 total_count` (references: OperationsRepository,
countQuery, change, ignore, count()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f40f79e-d04f-4fcf-885c-c304860c6d61

📥 Commits

Reviewing files that changed from the base of the PR and between 6669d98 and b3b9adf.

📒 Files selected for processing (1)
  • controlplane/src/core/repositories/OperationsRepository.ts

Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera left a comment

Choose a reason for hiding this comment

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

LGTM

@JivusAyrus JivusAyrus merged commit 41bb6b9 into main Mar 26, 2026
51 checks passed
@JivusAyrus JivusAyrus deleted the suvij/eng-9297-studio-add-server-side-pagination-to-operation-overrides branch March 26, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants