Repository navigation
fix(usage-analytics): bound dimension filter arrays and values - #8697
Conversation
Cap every dimension filter list at 500 values and every value at 256 characters in the shared filters schema, next to the existing organization scope cap, and export both limits. Add schema boundary tests for all twelve filter fields plus a procedure-level test that an over-limit filter answers BAD_REQUEST without reaching the Snowflake path.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThe change correctly bounds all twelve dimension filter lists (500 values) and each value (256 chars) in the shared Zod filters schema, propagates the limits to every procedure and the OpenAPI/MCP catalog schema, and covers the boundaries with schema- and procedure-level tests; no defects found in the changed code. Assumptions: Overview
Files Reviewed (4 files)
Verified the dashboard filter picker requests at most 100 suggestions per dimension ( Reviewed by deepseek-flash-latest · Input: 59.9K · Output: 8.9K · Cached: 553.5K Review guidance: REVIEW.md from base branch |
Evidence
Evidence: demonstrated — on the real Next runtime at this head, requests whose filter lists and values sit at the new limits reach the query path, and one value past either limit answers
400 BAD_REQUESTfrom input validation before the resolver runs. A same-stack differential against the branch base confirms the difference comes from the input-validation layer.Change
apps/web/src/routers/usage-analytics-schemas.tsgains two shared, exported limits next toMAX_SCOPE_ORGANIZATION_IDS:MAX_FILTER_VALUES= 500 — element count of every dimension filter list:features,models,modes,userIds,providers,projectsand eachexcluded*twin.MAX_FILTER_VALUE_LENGTH= 256 — length of each value in those lists.One local
FilterValuesSchemaapplies both limits, so all twelve fields stay in sync. Because the limits live in the shared filters schema, they covergetSummary,getTimeseries,getBreakdown,getTableand the OpenAPI input schema; a request outside them is rejected by input validation and answersBAD_REQUEST. The dashboard filter picker offers at most 100 suggestions per dimension, so a real selection stays well inside both limits.apps/web/src/routers/usage-analytics-router.tsre-exports the two constants next to the existing cap.Verification
pnpm --filter web exec jest --runInBand src/routers/usage-analytics-router.test.ts— 56 passed. Covers, for each of the twelve filter fields, at-the-limit accepted / one-over rejected for both count and value length, plus a procedure-levelBAD_REQUESTtest that also asserts no Snowflake call happens.pnpm --filter web exec jest --runInBand src/lib/openapi/trpc-openapi.test.ts— 9 passed.pnpm --filter web typecheck— clean.oxlint --config .oxlintrc.jsonandoxfmt --checkon the changed files — clean.Local dev stack (
pnpm dev:start nextjs, Compose Postgres + Redis) with a fake-login user,POST /api/trpc/usageAnalytics.getSummaryat this head:500 INTERNAL_SERVER_ERROR"Usage data temporarily unavailable", no analytics database in the local stack)400 BAD_REQUEST,too_bigwithmaximum: 500400 BAD_REQUEST,too_bigwithmaximum: 500400 BAD_REQUEST,too_bigwithmaximum: 500400 BAD_REQUEST,too_bigwithmaximum: 256Differential on the same running stack: the two schema files were swapped to the branch base, the same requests were repeated, then the change was restored and the requests repeated again. Only the input-validation answer changed; the query path behaved the same for inputs at the limits.
The same calls in the shape the dashboard uses (GET input, 30 values) also reach the query path, and
/usage?filter.include.feature=…with 30 values answers200.No visual change, so no screenshots.
Proxy answers
Answer: 500 per array
Confidence: 0.55
Answer: 256 chars
Confidence: 0.75
getTimeseriesdate window, hard total-binding maximum per statement)?Answer: No — brief only
Confidence: 0.65