Skip to content

eng-272 feat(crm): add normalized view query layer#382

Merged
Connorbelez merged 2 commits intomainfrom
eng-272
Apr 9, 2026
Merged

eng-272 feat(crm): add normalized view query layer#382
Connorbelez merged 2 commits intomainfrom
eng-272

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Apr 3, 2026

feat(crm): add normalized view query layer

responding to feedback

Summary by CodeRabbit

Release Notes

  • New Features

    • Calendar views now support date range filtering with granular time grouping (day/week/month).
    • View aggregates (sum, count, average, min, max) are now calculated and included in view responses.
    • User-defined saved view settings (column ordering, visibility, filtering) are now applied to view queries.
  • Bug Fixes

    • Fixed pagination cursor formatting.
    • Corrected audit log action type for record updates.
  • Tests

    • Expanded test coverage for view types and pagination scenarios.

@linear
Copy link
Copy Markdown

linear bot commented Apr 3, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

Warning

Rate limit exceeded

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

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 12 minutes and 28 seconds.

⌛ 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2ff5a6a-6018-430b-a91d-fab596dfc024

📥 Commits

Reviewing files that changed from the base of the PR and between 5482b67 and de0e130.

📒 Files selected for processing (7)
  • convex/crm/__tests__/viewEngine.test.ts
  • convex/crm/calendarQuery.ts
  • convex/crm/fieldDefs.ts
  • convex/crm/types.ts
  • convex/crm/viewQueries.ts
  • convex/crm/viewState.ts
  • convex/test/registerAuditLogComponent.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eng-272

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

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @Connorbelez, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copy link
Copy Markdown
Owner Author

Connorbelez commented Apr 3, 2026

This was referenced Apr 3, 2026
@Connorbelez Connorbelez marked this pull request as ready for review April 3, 2026 14:14
Copilot AI review requested due to automatic review settings April 3, 2026 14:14
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@Connorbelez Connorbelez changed the title feat(crm): add normalized view query layer eng-272 feat(crm): add normalized view query layer Apr 3, 2026
Copy link
Copy Markdown

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

Adds a normalized “view state” + query layer in the CRM backend so view queries can return richer, standardized payloads (columns/fields/view metadata, normalized rows, aggregates), while still supporting the existing rows compatibility shape.

Changes:

  • Introduces resolveViewState() and shared helpers in convex/crm/viewState.ts to centralize view definition + saved-view overlay resolution.
  • Updates crm.viewQueries.queryViewRecords to return normalized paging (page.rows) and aggregates, plus improved native/system-object pagination.
  • Extends calendar querying to return the same normalized metadata/rows/aggregates shape and adds tests for the new contracts.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
convex/test/registerAuditLogComponent.ts Registers additional aggregate components for audit-log testing.
convex/crm/viewState.ts New module centralizing view resolution, column normalization, row-building, and aggregates computation.
convex/crm/viewQueries.ts Refactors view querying to use resolved view state, adds normalized paging and aggregates, improves native/system pagination.
convex/crm/types.ts Adds types for normalized rows/cells, aggregates, and page results.
convex/crm/fieldDefs.ts Ensures isVisibleByDefault is always kept in sync with derived field metadata on update.
convex/crm/calendarQuery.ts Exposes queryCalendarViewData and expands calendar results with normalized metadata/rows/aggregates.
convex/crm/tests/viewEngine.test.ts Adds coverage for normalized payloads, saved-view overlays, calendar dispatch, and cursor behavior.
Comments suppressed due to low confidence (1)

convex/crm/calendarQuery.ts:367

  • validateCalendarView now calls resolveViewState(), which already verifies org context, viewDef ownership, and loads objectDef. The subsequent if (!viewDef || viewDef.orgId !== orgId) check and the extra ctx.db.get(viewDef.objectDefId) re-fetch are redundant (and the null-check is effectively unreachable). Consider relying on viewState.viewDef/viewState.objectDef directly to avoid duplicate reads and dead code paths.
async function validateCalendarView(
	ctx: CrmQueryCtx,
	viewDefId: Id<"viewDefs">,
	orgId: string
): Promise<ValidatedCalendarContext> {
	const viewState = await resolveViewState(ctx, viewDefId);
	const { viewDef } = viewState;
	if (!viewDef || viewDef.orgId !== orgId) {
		throw new ConvexError("View not found or access denied");
	}
	if (viewDef.viewType !== "calendar") {
		throw new ConvexError("View is not a calendar view");
	}
	if (viewDef.needsRepair) {
		throw new ConvexError("View needs repair before use");
	}

	const boundFieldId = viewDef.boundFieldId;
	if (!boundFieldId) {
		throw new ConvexError("Calendar view is missing a bound date field");
	}

	const objectDef = await ctx.db.get(viewDef.objectDefId);
	if (!objectDef || objectDef.orgId !== orgId || !objectDef.isActive) {
		throw new ConvexError("Object not found or access denied");
	}

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

Comment thread convex/crm/viewState.ts
Comment thread convex/crm/calendarQuery.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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
convex/crm/fieldDefs.ts (1)

278-285: Remove the duplicate patch.isVisibleByDefault assignment.

patch.isVisibleByDefault is set twice to the same value (Line 278 and Line 284). Keeping a single assignment avoids ambiguity during future edits.

Suggested cleanup
 		patch.isVisibleByDefault = effectiveFieldContract.isVisibleByDefault;
 		patch.normalizedFieldKind = effectiveFieldContract.normalizedFieldKind;
 		patch.rendererHint = effectiveFieldContract.rendererHint;
 		patch.layoutEligibility = effectiveFieldContract.layoutEligibility;
 		patch.aggregation = effectiveFieldContract.aggregation;
 		patch.editability = effectiveFieldContract.editability;
-		patch.isVisibleByDefault = effectiveFieldContract.isVisibleByDefault;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/crm/fieldDefs.ts` around lines 278 - 285, The patch object is
assigning patch.isVisibleByDefault twice from effectiveFieldContract; remove the
redundant assignment so patch.isVisibleByDefault is only set once (keep the
single assignment and delete the duplicate) in the block that also sets
patch.normalizedFieldKind, patch.rendererHint, patch.layoutEligibility,
patch.aggregation, and patch.editability to avoid ambiguity during future edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@convex/crm/calendarQuery.ts`:
- Around line 497-504: The code is re-querying base viewFilters (viewFilterRows)
instead of using the resolved filter set from resolveViewState(), which can
cause returned events to mismatch the payload's view metadata; replace the
query+parse flow so you use viewState.view.filters (the resolved filters) as the
source: remove the ctx.db.query("viewFilters")/withIndex call that produces
viewFilterRows, pass viewState.view.filters into parseViewFilters (or the
appropriate parser) along with fieldDefsById to get parsedFilters/skippedCount,
then call applyViewFilters(assembled, parsedFilters, fieldDefsById) as before so
the applied filters match the resolved viewState. Ensure you reference
resolveViewState(), viewState.view.filters, parseViewFilters, applyViewFilters,
assembled, and fieldDefsById when making the change.

In `@convex/crm/viewQueries.ts`:
- Around line 164-171: The function convertViewFiltersToRecordFilters currently
drops the logicalOperator from each ViewFilterDefinition; update it to copy
logicalOperator into the returned RecordFilter so table and kanban queries
preserve OR/AND semantics. In the convertViewFiltersToRecordFilters
implementation, include filter.logicalOperator in the mapped object (alongside
fieldDefId, operator from normalizeViewFilterOperator(filter.operator), and
value) so each RecordFilter carries the same logicalOperator as its source
ViewFilterDefinition. Ensure the function signature/types
(convertViewFiltersToRecordFilters, ViewFilterDefinition, RecordFilter) are
consistent with this added property.

In `@convex/crm/viewState.ts`:
- Around line 225-240: The current viewFilters.map block blindly JSON.parses
viewFilter.value which converts string values like "123"/"true"/"null" into
non-string types and breaks string-based matching; instead, use the field
context (lookup the field definition by viewFilter.fieldDefId from the
surrounding field definitions collection) and the viewFilter.operator to decide
parsing: if the field type or operator expects a numeric/boolean/null (e.g.,
number/date/boolean fields or operators like "in"/"gt"/"lt"), attempt JSON.parse
(with try/catch) and coerce appropriately; otherwise preserve the original
string value (no JSON.parse). Update the mapping code around viewFilters.map,
referencing viewFilter.fieldDefId, viewFilter.operator and the field definition
(e.g., fieldDef.type) to implement field/operator-aware parsing and fall back
safely to the original string on parse errors.
- Around line 496-503: The code currently returns the full record object in the
row (the `record` property) which leaks hidden fields; change the mapping that
returns `{ record, cells: ... }` to return a projected record that contains only
allowed metadata plus the visible fields used to build `cells`. Concretely, in
the block that maps `records` (referencing `records.map`, `visibleColumns`, and
`record`), construct a new `projectedRecord` (or similarly named object) that
copies only the record id/metadata and populates `fields` by iterating
`visibleColumns` and pulling `record.fields[column.name]`, then return that
`projectedRecord` instead of the original `record` so clients cannot access
hidden fields via `row.record.fields`.

---

Nitpick comments:
In `@convex/crm/fieldDefs.ts`:
- Around line 278-285: The patch object is assigning patch.isVisibleByDefault
twice from effectiveFieldContract; remove the redundant assignment so
patch.isVisibleByDefault is only set once (keep the single assignment and delete
the duplicate) in the block that also sets patch.normalizedFieldKind,
patch.rendererHint, patch.layoutEligibility, patch.aggregation, and
patch.editability to avoid ambiguity during future edits.
🪄 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: a5e7b350-8a78-4107-af9f-d66f8da48f47

📥 Commits

Reviewing files that changed from the base of the PR and between f90c34f and 5482b67.

📒 Files selected for processing (7)
  • convex/crm/__tests__/viewEngine.test.ts
  • convex/crm/calendarQuery.ts
  • convex/crm/fieldDefs.ts
  • convex/crm/types.ts
  • convex/crm/viewQueries.ts
  • convex/crm/viewState.ts
  • convex/test/registerAuditLogComponent.ts

Comment thread convex/crm/calendarQuery.ts
Comment thread convex/crm/viewQueries.ts
Comment thread convex/crm/viewState.ts
Comment thread convex/crm/viewState.ts
Copy link
Copy Markdown
Owner Author

Connorbelez commented Apr 9, 2026

Merge activity

  • Apr 9, 6:12 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 9, 6:14 PM UTC: Graphite rebased this pull request as part of a merge.
  • Apr 9, 6:14 PM UTC: @Connorbelez merged this pull request with Graphite.

@Connorbelez Connorbelez changed the base branch from eng-271 to graphite-base/382 April 9, 2026 18:12
@Connorbelez Connorbelez changed the base branch from graphite-base/382 to main April 9, 2026 18:12
@Connorbelez Connorbelez merged commit c474ff8 into main Apr 9, 2026
1 of 3 checks passed
This was referenced Apr 9, 2026
Connorbelez added a commit that referenced this pull request Apr 20, 2026
feat(crm): add normalized view query layer

responding to feedback

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Calendar views now support date range filtering with granular time grouping (day/week/month).
  * View aggregates (sum, count, average, min, max) are now calculated and included in view responses.
  * User-defined saved view settings (column ordering, visibility, filtering) are now applied to view queries.

* **Bug Fixes**
  * Fixed pagination cursor formatting.
  * Corrected audit log action type for record updates.

* **Tests**
  * Expanded test coverage for view types and pagination scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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