Skip to content

fix: version list endpoint respects limit param#1119

Merged
adityachoudhari26 merged 2 commits into
mainfrom
version-cel-respects-limit
May 11, 2026
Merged

fix: version list endpoint respects limit param#1119
adityachoudhari26 merged 2 commits into
mainfrom
version-cel-respects-limit

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented May 11, 2026

fixes #1118

Summary by CodeRabbit

  • New Features

    • Enhanced deployment version listing with improved filtering and pagination performance for large datasets.
  • Tests

    • Added comprehensive test suite for deployment version filtering, pagination, and query isolation.

Review Change Stack

Copilot AI review requested due to automatic review settings May 11, 2026 18:18
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@adityachoudhari26 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 57 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a649c7c9-a08b-4a7f-899e-76d6b7f984ff

📥 Commits

Reviewing files that changed from the base of the PR and between 27af3b1 and 783916e.

📒 Files selected for processing (1)
  • apps/api/src/routes/v1/workspaces/deployments.ts
📝 Walkthrough

Walkthrough

The deployment version listing endpoint refactors CEL filtering from a fixed in-memory approach (1000 candidates) to bounded batch scanning with incremental CEL evaluation and pagination. The handler now uses a new helper that scans deploymentVersion rows in configurable windows, evaluates the CEL expression per batch, and streams matched results while respecting the caller's limit and offset.

Changes

CEL Batch-Scan Deployment Version Listing

Layer / File(s) Summary
Constants and Helper Function
apps/api/src/routes/v1/workspaces/deployments.ts
Introduces CEL_BATCH_SIZE and CEL_MAX_SCAN constants; adds listDeploymentVersionsMatchingCel(...) helper that paginates CEL matches by repeatedly fetching deploymentVersion windows in createdAt order, filtering each batch with the existing CEL evaluator, and returning accumulated items and total matched count.
Handler Integration
apps/api/src/routes/v1/workspaces/deployments.ts
Updates versions listing handler to call listDeploymentVersionsMatchingCel when CEL is provided, replacing fixed-1000-candidate in-memory filtering; non-CEL path remains direct DB pagination.
Test Infrastructure
e2e/package.json, e2e/tests/api/deployment-version-list.spec.ts
Adds Playwright test scripts for deployment-version-list.spec.ts; establishes e2e test suite with system and deployment lifecycle, DB cleanup, and createDeployment helper.
Test Coverage
e2e/tests/api/deployment-version-list.spec.ts
Validates multi-batch CEL scanning with correct total, deep-offset pagination, limit-exceeds-matches behavior, empty result sets, ascending and descending ordering, invalid CEL error handling, non-CEL listing, and query isolation across deployments.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Handler
  participant ListHelper as listDeploymentVersionsMatchingCel
  participant DB as Database
  participant CELEval as CEL Evaluator
  Client->>Handler: GET /versions?cel=filter&limit=10&offset=0
  Handler->>ListHelper: listDeploymentVersionsMatchingCel(cel, 10, 0, "desc")
  loop Scan batches until 10 items or scan cap
    ListHelper->>DB: SELECT * FROM deploymentVersion<br/>ORDER BY createdAt DESC<br/>LIMIT CEL_BATCH_SIZE OFFSET window_offset
    DB-->>ListHelper: batch of rows
    ListHelper->>CELEval: filterDeploymentVersions(batch, cel)
    CELEval-->>ListHelper: matching rows
    ListHelper->>ListHelper: accumulate items, increment total count
  end
  ListHelper-->>Handler: {items: [...10 matches], total: 750}
  Handler-->>Client: {items, total, limit, offset}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ctrlplanedev/ctrlplane#1047: Prior PR implemented CEL filtering with a fixed 1000-candidate in-memory approach; this PR refactors that to use bounded batch scanning and streaming to respect the LIMIT query parameter.

Poem

🐰 Hop! The batch-scan hops so merry,
CEL matches it does carry,
No more fixed thousand in the fold,
Respect the LIMIT—crisp and bold! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: version list endpoint respects limit param' directly reflects the main change: the endpoint now respects the limit parameter after CEL filtering instead of before.
Linked Issues check ✅ Passed The PR successfully addresses issue #1118 by implementing CEL-filtered batch scanning with a bounded scan cap, ensuring the returned result set respects the provided limit parameter.
Out of Scope Changes check ✅ Passed All changes are directly related to the issue: the deployments endpoint implementation, test suite for CEL paging behavior, and supporting test scripts—no out-of-scope modifications detected.

✏️ 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 version-cel-respects-limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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: 3

🤖 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 `@apps/api/src/routes/v1/workspaces/deployments.ts`:
- Around line 329-332: The ordering uses only schema.deploymentVersion.createdAt
which can produce unstable pagination; update the orderBy expression (the const
orderBy used around the deploymentVersion query) to include a deterministic
tiebreaker by adding the unique id field as a secondary sort key (e.g.
schema.deploymentVersion.id) in both branches (asc and desc). Apply the same
change to the other occurrence around lines 372-375 so both ascending and
descending sorts use [createdAt, id] (or equivalent multi-column ordering) to
ensure stable pagination.
- Around line 339-359: The loop that pages through deploymentVersion using
CEL_MAX_SCAN can stop early and return an incorrect total (it currently returns
{ items, total: matched })—update the function that iterates over
schema.deploymentVersion (the while loop using CEL_MAX_SCAN, CEL_BATCH_SIZE,
scanned, matched and filterDeploymentVersions) to detect when the scan cap was
hit (scanned >= CEL_MAX_SCAN or batch.length === CEL_BATCH_SIZE on final
iteration) and surface truncation instead of returning a potentially wrong
total: either return a nullable/unknown total (e.g., total: null) or add a
truncated flag (e.g., truncated: true) alongside total so callers know the count
is incomplete; ensure the change is applied where the function currently returns
{ items, total: matched } and document the new contract for consumers.

In `@e2e/tests/api/deployment-version-list.spec.ts`:
- Around line 15-47: Replace the imperative setup/teardown in
test.beforeAll/test.afterAll by declaring the base entities in a companion
.spec.yaml and using the existing importEntitiesFromYaml and
cleanupImportedEntities helpers; remove the manual POST to create systems (and
the manual DELETE loop and direct DB deletion of schema.deploymentVersion) and
instead call importEntitiesFromYaml at the start of the suite to obtain systemId
and deploymentIds, then call cleanupImportedEntities in afterAll to tear them
down. Ensure references to deploymentIds and systemId are populated from the
import response and remove any direct DB.delete(schema.deploymentVersion) usage
so cleanup is handled by cleanupImportedEntities.
🪄 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

Run ID: 7eff9208-3338-4f7e-b692-e7ea5aee9b34

📥 Commits

Reviewing files that changed from the base of the PR and between f0da3d6 and 27af3b1.

📒 Files selected for processing (3)
  • apps/api/src/routes/v1/workspaces/deployments.ts
  • e2e/package.json
  • e2e/tests/api/deployment-version-list.spec.ts

Comment thread apps/api/src/routes/v1/workspaces/deployments.ts Outdated
Comment on lines +339 to +359
while (scanned < CEL_MAX_SCAN) {
const batch = await db
.select()
.from(schema.deploymentVersion)
.where(eq(schema.deploymentVersion.deploymentId, deploymentId))
.orderBy(orderBy)
.limit(CEL_BATCH_SIZE)
.offset(scanned);
if (batch.length === 0) break;

for (const version of filterDeploymentVersions(batch, cel)) {
if (matched >= offset && matched < pageEnd) items.push(version);
matched++;
}

scanned += batch.length;
if (batch.length < CEL_BATCH_SIZE) break;
}

return { items, total: matched };
}
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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

CEL_MAX_SCAN can silently return partial total and incomplete pages.

When the scan cap is reached, the function returns total: matched as if complete, even though more matching rows may exist beyond the cap. That breaks total correctness and can hide valid results at higher offsets. Please surface truncation explicitly (or adopt a continuation strategy that preserves correctness).

🤖 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 `@apps/api/src/routes/v1/workspaces/deployments.ts` around lines 339 - 359, The
loop that pages through deploymentVersion using CEL_MAX_SCAN can stop early and
return an incorrect total (it currently returns { items, total: matched
})—update the function that iterates over schema.deploymentVersion (the while
loop using CEL_MAX_SCAN, CEL_BATCH_SIZE, scanned, matched and
filterDeploymentVersions) to detect when the scan cap was hit (scanned >=
CEL_MAX_SCAN or batch.length === CEL_BATCH_SIZE on final iteration) and surface
truncation instead of returning a potentially wrong total: either return a
nullable/unknown total (e.g., total: null) or add a truncated flag (e.g.,
truncated: true) alongside total so callers know the count is incomplete; ensure
the change is applied where the function currently returns { items, total:
matched } and document the new contract for consumers.

Comment on lines +15 to +47
test.beforeAll(async ({ api, workspace }) => {
const systemRes = await api.POST(
"/v1/workspaces/{workspaceId}/systems",
{
params: { path: { workspaceId: workspace.id } },
body: { name: `ver-list-system-${faker.string.alphanumeric(8)}` },
},
);
expect(systemRes.response.status).toBe(202);
systemId = systemRes.data!.id;
});

test.afterAll(async ({ api, workspace }) => {
// FK cascade from deployment → deployment_version was dropped in migration
// 0139, so versions inserted directly must be deleted explicitly before
// their owning deployment.
if (deploymentIds.length > 0) {
await db
.delete(schema.deploymentVersion)
.where(inArray(schema.deploymentVersion.deploymentId, deploymentIds));

for (const deploymentId of deploymentIds) {
await api.DELETE(
"/v1/workspaces/{workspaceId}/deployments/{deploymentId}",
{ params: { path: { workspaceId: workspace.id, deploymentId } } },
);
}
}

await api.DELETE("/v1/workspaces/{workspaceId}/systems/{systemId}", {
params: { path: { workspaceId: workspace.id, systemId } },
});
});
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.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Align this E2E setup/teardown with YAML fixture workflow.

This spec currently provisions and cleans entities imperatively. Please migrate the base entities to .spec.yaml + importEntitiesFromYaml/cleanupImportedEntities to match the repo’s E2E fixture contract and reduce bespoke cleanup logic.

As per coding guidelines, "E2E tests use YAML fixture files (.spec.yaml alongside .spec.ts) to declare test entities; use importEntitiesFromYaml to load them and cleanupImportedEntities to tear them down".

🤖 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 `@e2e/tests/api/deployment-version-list.spec.ts` around lines 15 - 47, Replace
the imperative setup/teardown in test.beforeAll/test.afterAll by declaring the
base entities in a companion .spec.yaml and using the existing
importEntitiesFromYaml and cleanupImportedEntities helpers; remove the manual
POST to create systems (and the manual DELETE loop and direct DB deletion of
schema.deploymentVersion) and instead call importEntitiesFromYaml at the start
of the suite to obtain systemId and deploymentIds, then call
cleanupImportedEntities in afterAll to tear them down. Ensure references to
deploymentIds and systemId are populated from the import response and remove any
direct DB.delete(schema.deploymentVersion) usage so cleanup is handled by
cleanupImportedEntities.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes the deployment version list endpoint behavior when cel is provided so that limit/offset are applied to the filtered result set (instead of limiting the DB query first and then filtering in-memory), addressing issue #1118.

Changes:

  • Added batched scanning logic for CEL-filtered deployment version listing so paging is applied over matches.
  • Added comprehensive Playwright E2E coverage for CEL paging (including multi-batch behavior) and non-CEL paging.
  • Added pnpm scripts to run the new deployment-version list E2E tests (including a heavy opt-in variant).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
apps/api/src/routes/v1/workspaces/deployments.ts Implements batched CEL scanning to make limit/offset apply to filtered matches and returns a match count.
e2e/tests/api/deployment-version-list.spec.ts Adds E2E tests validating CEL paging correctness, ordering, totals, and isolation between deployments.
e2e/package.json Adds convenience scripts to run the new E2E test suite (standard and heavy).
Comments suppressed due to low confidence (1)

apps/api/src/routes/v1/workspaces/deployments.ts:381

  • The non-CEL list path also queries/counts deployment_version rows using only deploymentId and ignores workspaceId from the route params. For tenant isolation consistency with other endpoints in this file, scope these queries to the requested workspace (e.g., join deployment and filter by deployment.workspaceId).
    const { total } = await db
      .select({ total: count() })
      .from(schema.deploymentVersion)
      .where(eq(schema.deploymentVersion.deploymentId, deploymentId))
      .then(takeFirst);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +340 to +344
const batch = await db
.select()
.from(schema.deploymentVersion)
.where(eq(schema.deploymentVersion.deploymentId, deploymentId))
.orderBy(orderBy)
Comment on lines +329 to +333
const orderBy =
order === "asc"
? asc(schema.deploymentVersion.createdAt)
: desc(schema.deploymentVersion.createdAt);

Comment on lines +319 to +321
const CEL_BATCH_SIZE = 500;
const CEL_MAX_SCAN = 100_000;

Comment on lines +37 to +46
await api.DELETE(
"/v1/workspaces/{workspaceId}/deployments/{deploymentId}",
{ params: { path: { workspaceId: workspace.id, deploymentId } } },
);
}
}

await api.DELETE("/v1/workspaces/{workspaceId}/systems/{systemId}", {
params: { path: { workspaceId: workspace.id, systemId } },
});
@adityachoudhari26 adityachoudhari26 merged commit c2fd344 into main May 11, 2026
9 checks passed
@adityachoudhari26 adityachoudhari26 deleted the version-cel-respects-limit branch May 11, 2026 19:00
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.

fix: LIMIT not respected when cel is provided for version list api endpoint

4 participants