Skip to content

fix(orgusers): support case-insensitive name filtering#1723

Open
Shreyag02 wants to merge 4 commits into
mainfrom
fix/org-users-filter-by-name
Open

fix(orgusers): support case-insensitive name filtering#1723
Shreyag02 wants to merge 4 commits into
mainfrom
fix/org-users-filter-by-name

Conversation

@Shreyag02

Copy link
Copy Markdown
Contributor

Summary

Fixes "unable to filter by name" on the organization users list. The non-rolefilter builder did not support the ilike/notilike operators, so a name filter sent as ilike was rejected with wrapBadOperatorError and the filter silently failed. This adds ilike/notilike support and, while there, hardens operator handling by routing on the column's declared datatype so string-only operators can no longer be applied to the datetime org_joined_at column.

Changes

  • Add ilike/notilike operator support for string columns.
  • Route non-role filters by the column's declared datatype (rql.GetDataTypeOfField) instead of one combined operator list.
  • Restrict the datetime org_joined_at column to eq/neq/gt/lt/gte/lte; any other operator (including like/ilike/in/empty) now returns wrapBadOperatorError.
  • Add tests for ilike/notilike, a valid datetime operator (gte), and ilike rejected on the datetime column.

Technical Details

buildNonRoleFilterCondition looks up the field's datatype up front:

  • datetime columns accept only eq/neq/gt/lt/gte/lte.
  • string columns accept eq/neq/like/ilike/in/notin/notlike/notilike/empty/notempty.

like/notlike wrap the value in %...% themselves; ilike/notilike expect the caller to include the wildcards (e.g. %foo%). The local operator lists mirror salt/rql's upstream validString/DatetimeOperations, so the new branches are reachable from the API. Side effect: an unknown field name now fails fast via GetDataTypeOfField instead of falling through to the raw column name.

Test Plan

  • Manual testing completed
  • Build and type checking passes

SQL Safety (if your PR touches *_repository.go or goqu.*)

  • Values flow through ? placeholders, goqu.Ex{}, or goqu.Record{} — never fmt.Sprintf or + building a query that gets executed.
  • ToSQL() callers capture and forward params (query, params, err := stmt.ToSQL(); db.…Context(ctx, …, query, params...)). Never query, _, err := ….
  • No ? placeholders inside single-quoted SQL literals in goqu.L (use make_interval(hours => ?)-style functions instead).
  • Any //nolint:forbidigo or // #nosec G20x annotation has a one-line justification on the same line that a reviewer can verify.

@Shreyag02 Shreyag02 requested a review from whoAbhishekSah July 1, 2026 07:47
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Jul 2, 2026 10:11am

@Shreyag02 Shreyag02 requested a review from rohanchkrabrty July 1, 2026 07:47
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e4050f76-4419-475c-a9ba-83caf0d2d3ac

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdbd0c and 291b871.

📒 Files selected for processing (1)
  • internal/store/postgres/org_users_repository.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/store/postgres/org_users_repository.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved RQL filtering for text fields by adding support for ilike and notilike (case-insensitive matching).
    • Corrected like/notlike to generate LIKE/NOT LIKE SQL and use the provided value directly (no implicit wildcard behavior).
    • Strengthened date/time filter handling by restricting allowed operators and generating the proper comparisons.
  • Tests
    • Expanded and updated unit tests to cover ilike/notilike, adjusted like/notlike SQL expectations, and added datetime operator validation cases.

Walkthrough

buildNonRoleFilterCondition now routes operator handling by field datatype, adds ilike/notilike for string filters, and uses direct comparisons for datetime filters. Tests were updated for the revised string SQL output and datetime operator validation.

Changes

Non-role Filter Operator Validation

Layer / File(s) Summary
Datatype-based operator validation and condition construction
internal/store/postgres/org_users_repository.go
Resolves field datatype before validating operators, limits datetime comparisons, and generates string comparisons with LIKE/NOT LIKE/ILIKE/NOT ILIKE using filter.Value directly.
Operator and datatype test coverage
internal/store/postgres/org_users_repository_test.go
Updates string operator expectations, adds ilike/notilike coverage, and adds datetime gte plus invalid-operator assertions for org_joined_at.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • raystack/frontier#1547: Both PRs touch buildNonRoleFilterCondition in internal/store/postgres/org_users_repository.go for non-role filter operator handling.

Suggested reviewers: rohilsurana

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
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.

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.

@coveralls

coveralls commented Jul 1, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 28582274495

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+1.0%) to 44.847%

Details

  • Coverage increased (+1.0%) from the base build.
  • Patch coverage: 3 uncovered changes across 1 file (21 of 24 lines covered, 87.5%).
  • 443 coverage regressions across 16 files.

Uncovered Changes

File Changed Covered %
internal/store/postgres/org_users_repository.go 24 21 87.5%

Coverage Regressions

443 previously-covered lines in 16 files lost coverage.

Top 10 Files by Coverage Loss Lines Losing Coverage Coverage
core/serviceuser/service.go 68 46.31%
core/user/service.go 65 67.28%
internal/store/postgres/userpat_repository.go 54 30.83%
internal/bootstrap/service.go 53 29.5%
internal/store/postgres/serviceuser_repository.go 37 0.0%
internal/bootstrap/schema/schema.go 35 13.79%
cmd/serve.go 29 0.0%
core/authenticate/authenticators.go 20 46.89%
internal/api/v1beta1connect/serviceuser.go 20 91.34%
core/userpat/alert_service.go 15 77.09%

Coverage Stats

Coverage Status
Relevant Lines: 37617
Covered Lines: 16870
Line Coverage: 44.85%
Coverage Strength: 12.49 hits per line

💛 - Coveralls

searchPattern := "%" + filter.Value.(string) + "%"
return goqu.Cast(goqu.I(columnName), "TEXT").NotILike(searchPattern), nil
case "ilike":
// case-insensitive match; the frontend already includes the wildcards (e.g. "%foo%")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not follow the same pattern? In one case, the frontend adds %..%, and in another, it does not. We should keep it consistent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both the ilike operator and the %..% wildcards are added by Apsara's DataTable filter transform before the request leaves the browser — not by our app code.

In @raystack/apsara (components/data-table/utils/), transformToDataTableQuery builds each filter via two helpers (filter-operations.tsx):

  • getFilterOperator maps the UI string operators to ilike:
    if (filterType === FilterType.string &&
        (operator === 'contains' || operator === 'starts_with' || operator === 'ends_with')) {
      return 'ilike';
    }
  • getFilterValuehandleStringBasedTypes bakes the wildcards into the value:
    if (operator === 'contains')     processedValue = `%${stringVal}%`;
    else if (operator === 'starts_with') processedValue = `${stringVal}%`;
    else if (operator === 'ends_with')   processedValue = `%${stringVal}`;
    else if (operator === 'ilike') { if (!stringVal.includes('%')) processedValue = `%${stringVal}%`; }

So for the org-users name filter the client always sends { operator: "ilike", value: "%foo%" } — never like. That's why the ilike branch here treats wildcards as caller-provided.

You're right that it's inconsistent, though: like/ilike both compile to SQL ILIKE (via goqu .ILike()), and since the real caller only ever emits ilike with wildcards pre-added, the like/notlike branches that wrap server-side are effectively dead for this list. I'll align them to one convention — keeping ilike/notilike (caller owns the wildcards) and dropping the server-side wrapping in like/notlike — so there's a single, consistent path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — wildcards are now caller-owned for all four operators (like/notlike/ilike/notilike), so there is a single consistent convention. The server no longer wraps %..% on like/notlike; the caller (Apsara DataTable, which only ever emits ilike with wildcards pre-baked) owns them in every case. This matches org_pats_repository/org_billing_repository.

Updated the like/notlike tests accordingly to pass pre-wrapped values.

case "like":
// case-insensitive match; wildcards are added here
searchPattern := "%" + filter.Value.(string) + "%"
return goqu.Cast(goqu.I(columnName), "TEXT").ILike(searchPattern), nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Like also uses iLIKE only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct — goqu's .ILike() emits SQL ILIKE, so like here is already case-insensitive. The only real difference between the like and ilike branches is who owns the %..% wildcards (server-side for like, caller-side for ilike). See my note on the ilike case below — Apsara only ever sends ilike with wildcards baked in, so I'll collapse these to one convention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed. I aligned this file to the same convention org_pats_repository and org_billing_repository already use (the two other repos that own wildcards caller-side + cast to TEXT):

case "like":     return goqu.Cast(goqu.I(columnName), "TEXT").Like(filter.Value.(string)), nil     // case-sensitive
case "notlike":  return goqu.Cast(goqu.I(columnName), "TEXT").NotLike(filter.Value.(string)), nil
case "ilike":    return goqu.Cast(goqu.I(columnName), "TEXT").ILike(filter.Value.(string)), nil    // case-insensitive
case "notilike": return goqu.Cast(goqu.I(columnName), "TEXT").NotILike(filter.Value.(string)), nil

So like now maps to real SQL LIKE (case-sensitive) instead of ILIKE, and ilike stays ILIKE. No more silent case-insensitivity on like.

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4f879278-05b7-407a-92d3-60dce59bd68d

📥 Commits

Reviewing files that changed from the base of the PR and between 90a5531 and 1b8676e.

📒 Files selected for processing (2)
  • internal/store/postgres/org_users_repository.go
  • internal/store/postgres/org_users_repository_test.go
💤 Files with no reviewable changes (1)
  • internal/store/postgres/org_users_repository_test.go

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4f879278-05b7-407a-92d3-60dce59bd68d

📥 Commits

Reviewing files that changed from the base of the PR and between 90a5531 and 1b8676e.

📒 Files selected for processing (2)
  • internal/store/postgres/org_users_repository.go
  • internal/store/postgres/org_users_repository_test.go
💤 Files with no reviewable changes (1)
  • internal/store/postgres/org_users_repository_test.go
🛑 Comments failed to post (1)
internal/store/postgres/org_users_repository.go (1)

327-337: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A6 'org_joined_at' internal/store/postgres/org_users_repository_test.go

Repository: raystack/frontier

Length of output: 6564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- org_users_repository.go (relevant function) ---'
sed -n '327,380p' internal/store/postgres/org_users_repository.go | cat -n

echo
echo '--- org_users_repository_test.go (org_joined_at-related tests) ---'
sed -n '150,230p' internal/store/postgres/org_users_repository_test.go | cat -n

echo
echo '--- search for org_joined_at + ilike / empty / notempty / bad operator cases ---'
rg -n -A4 -B4 'org_joined_at|ilike|empty|notempty|wrapBadOperatorError|GetDataTypeOfField' internal/store/postgres org_users_repository_test.go

Repository: raystack/frontier

Length of output: 50374


Datatype routing is missing for org_joined_at. buildNonRoleFilterCondition still applies the same operator allowlist to every field, then maps org_joined_at to policies.created_at without checking the field type. That lets like/ilike/in/empty through on a datetime column instead of rejecting them; add a rejection test for org_joined_at + ilike/empty.

Source: Learnings

@Shreyag02

Copy link
Copy Markdown
Contributor Author

@coderabbitai Addressed the datatype-routing finding. buildNonRoleFilterCondition now routes by the field's declared datatype again via rql.GetDataTypeOfField(filter.Name, svc.AggregatedUser{}):

  • org_joined_at (tagged type=datetime) only accepts eq/neq/gt/lt/gte/lte; string operators (like/ilike/in/empty/…) are rejected with wrapBadOperatorError instead of generating bogus SQL against policies.created_at.
  • String columns keep the full string-operator set.

Restored the org_joined_at + ilike rejection test and added an org_joined_at + empty rejection test.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cd /tmp && rm -rf frontier && git clone --depth 50 https://github.com/raystack/frontier.git frontier 2>&1 | tail -5
cd frontier && git fetch origin pull/1723/head:pr1723 2>&1 | tail -5 && git checkout pr1723 2>&1 | tail -5
sed -n '1,400p' internal/store/postgres/org_users_repository.go | grep -n "buildNonRoleFilterCondition" -A 80

Length of output: 6890


Confirmed — the updated buildNonRoleFilterCondition (Line 327 onward) correctly routes on rql.GetDataTypeOfField(filter.Name, svc.AggregatedUser{}): org_joined_at (datatype datetime) is now restricted to eq/neq/gt/lt/gte/lte and rejects everything else with wrapBadOperatorError, while string columns retain the full operator set including ilike/notilike. This matches what you described and resolves the earlier datatype-routing concern.

@Shreyag02 Shreyag02 requested a review from whoAbhishekSah July 2, 2026 09:22
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.

3 participants