Skip to content

feat(search): match profile id in profiles and sessions search - #355

Open
muralikrishna13-dash wants to merge 2 commits into
mainfrom
feat/profile-id-search
Open

feat(search): match profile id in profiles and sessions search#355
muralikrishna13-dash wants to merge 2 commits into
mainfrom
feat/profile-id-search

Conversation

@muralikrishna13-dash

@muralikrishna13-dash muralikrishna13-dash commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

Profile search only matches email, first_name, and last_name. Our users are identified by backend_userid and typically have no email or name set, so searching an ID always returns No profiles — the only way to open a user was to paste the profile id into the URL manually.

Change

  • Profiles (getProfileList / getProfileListCount): search now also matches id (partial match works, e.g. a userid prefix).
  • Sessions (getSessionList / getSessionsCount): search now also matches profile_id.
  • Search patterns are now escaped via sqlstring.escape — the profiles and sessions-count clauses previously interpolated the raw search string into the ILIKE pattern.

Testing

Verified locally against dev ClickHouse: searching a full and partial profile id on the Profiles page (identified tab) returns the matching profile; email/name search behaves as before.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved search matching across profile and session lists by also considering additional fields such as last names and profile/ID-style inputs.
    • Search behavior is now consistent between list views and their corresponding count results.
    • Enhanced safety for special characters in search input, reducing unexpected matches or broken queries.

Profile search only matched email/first/last name — useless for
backend_userid-identified users with no email or name. Sessions search
now also matches profile_id. Patterns escaped via sqlstring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Search filter logic in profile and session database services was updated. Profile search now escapes the search pattern, matches additional profile fields, and treats ID-shaped input as a prefix match. Session search now switches between profile ID prefix matching and escaped field matching.

Changes

Search Filter Updates

Layer / File(s) Summary
Profile search filter expansion
packages/db/src/services/profile.service.ts
looksLikeProfileId and getProfileSearchWhereClause centralize profile search clause construction, and both profile list queries now use the shared escaped clause.
Session search filter expansion
packages/db/src/services/session.service.ts
getSessionList and getSessionsCount use looksLikeProfileId to choose between profile_id prefix matching and escaped ILIKE matching across entry, exit, and referrer fields.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: search now matches profile IDs in profiles and sessions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/profile-id-search

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/db/src/services/profile.service.ts (1)

205-206: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated escape+wildcard pattern logic.

The sqlstring.escape(%${search}%) pattern-building line is repeated identically in both functions here, and again (twice) in session.service.ts. Consider extracting a small shared helper (e.g. buildSearchPattern(search: string)) to keep the four call sites consistent if the escaping strategy ever changes.

♻️ Proposed helper
+export function buildLikePattern(search: string) {
+  return sqlstring.escape(`%${search}%`);
+}
   if (search) {
-    const pattern = sqlstring.escape(`%${search}%`);
+    const pattern = buildLikePattern(search);
     sb.where.search = `(id ILIKE ${pattern} OR email ILIKE ${pattern} OR first_name ILIKE ${pattern} OR last_name ILIKE ${pattern})`;

Also applies to: 226-227

🤖 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 `@packages/db/src/services/profile.service.ts` around lines 205 - 206, The
search pattern escaping and wildcard wrapping logic is duplicated in
`profile.service.ts` and also in the matching `session.service.ts` call sites,
so extract it into a shared helper like `buildSearchPattern(search: string)` and
use that helper from the affected search-building code paths. Keep the existing
behavior the same by centralizing the `sqlstring.escape` plus `%...%`
construction in one place, and update the relevant methods in
`profile.service.ts` to call the helper instead of repeating the pattern inline.
🤖 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.

Nitpick comments:
In `@packages/db/src/services/profile.service.ts`:
- Around line 205-206: The search pattern escaping and wildcard wrapping logic
is duplicated in `profile.service.ts` and also in the matching
`session.service.ts` call sites, so extract it into a shared helper like
`buildSearchPattern(search: string)` and use that helper from the affected
search-building code paths. Keep the existing behavior the same by centralizing
the `sqlstring.escape` plus `%...%` construction in one place, and update the
relevant methods in `profile.service.ts` to call the helper instead of repeating
the pattern inline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 262daf57-1f7a-4d36-b632-84b79a589f9c

📥 Commits

Reviewing files that changed from the base of the PR and between de02208 and 073ca8a.

📒 Files selected for processing (2)
  • packages/db/src/services/profile.service.ts
  • packages/db/src/services/session.service.ts

ILIKE '%…%' scans every row of the project — email/name search already
times out on large projects, and adding id to the same disjunction
inherits that. Instead, single-token searches (16+ chars, no @/spaces —
uids/uuids/cuids) now short-circuit to a prefix LIKE on id, which prunes
via the (project_id, id) primary key regardless of table size. Email and
name search is unchanged from main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@muralikrishna13-dash

Copy link
Copy Markdown
Collaborator Author

Good catch — reworked in 3fb4a12. You're right that adding id to the same ILIKE '%…%' disjunction inherits the full-scan cost that already makes email/name search time out on dashreels/shortfree.

New approach: ID-shaped searches never touch the ILIKE path at all. A single-token query (16+ chars, no @/spaces — matches firebase uids, uuids, cuids) short-circuits to id LIKE '<query>%' — no leading wildcard, so ClickHouse prunes via the (project_id, id) primary key. That's an index lookup regardless of table size, and it covers both exact ids and pasted prefixes. Sessions get the same branch on profile_id (single narrow-column comparison instead of a 5-column ILIKE).

Email/name search is byte-for-byte what's on main today — this PR no longer changes that path (its slowness needs a separate fix, e.g. an ngram bloom-filter index on the profiles table, happy to scope that separately).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
packages/db/src/services/session.service.ts (1)

186-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate search-clause logic; extract a shared helper.

The id-shaped branching (looksLikeProfileIdprofile_id LIKE prefix vs. escaped ILIKE over path/referrer fields) is copy-pasted identically between getSessionList and getSessionsCount. profile.service.ts already established the pattern of centralizing this into a getXSearchWhereClause helper — mirror that here to avoid drift between the two call sites.

Also note: this duplicated logic inherits the unescaped LIKE-metacharacter issue flagged in profile.service.ts (Lines 157-174) — _ in search will act as a wildcard for both profile_id and the path/referrer fields.

♻️ Proposed refactor: extract shared helper
+function getSessionSearchWhereClause(search: string) {
+  if (looksLikeProfileId(search)) {
+    return `profile_id LIKE ${sqlstring.escape(`${search}%`)}`;
+  }
+  const s = sqlstring.escape(`%${search}%`);
+  return `(entry_path ILIKE ${s} OR exit_path ILIKE ${s} OR referrer ILIKE ${s} OR referrer_name ILIKE ${s})`;
+}

Then in both getSessionList and getSessionsCount:

   if (search) {
-    if (looksLikeProfileId(search)) {
-      sb.where.search = `profile_id LIKE ${sqlstring.escape(`${search}%`)}`;
-    } else {
-      const s = sqlstring.escape(`%${search}%`);
-      sb.where.search = `(entry_path ILIKE ${s} OR exit_path ILIKE ${s} OR referrer ILIKE ${s} OR referrer_name ILIKE ${s})`;
-    }
+    sb.where.search = getSessionSearchWhereClause(search);
   }

Also applies to: 315-320

🤖 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 `@packages/db/src/services/session.service.ts` around lines 186 - 192, The
search-clause logic in getSessionList and getSessionsCount is duplicated, so
extract it into a shared helper (following the getXSearchWhereClause pattern
used in profile.service.ts) and have both call sites use it. While doing so,
also fix the LIKE wildcard handling by escaping user input for both the
profile_id prefix match and the ILIKE path/referrer search so characters like _
do not act as wildcards. Use the existing looksLikeProfileId branch and the
profile_id/search field names to locate the shared logic.
🤖 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 `@packages/db/src/services/profile.service.ts`:
- Around line 157-174: The ID-search branch in getProfileSearchWhereClause
treats `_` as a SQL LIKE wildcard because sqlstring.escape only quotes the
string and does not escape LIKE metacharacters. Update the ID-prefix query in
profile.service.ts to escape LIKE wildcards before appending the trailing % so
exact ID-shaped searches like user_12345 do not overmatch, and apply the same
fix to the analogous search logic in session.service.ts.

---

Nitpick comments:
In `@packages/db/src/services/session.service.ts`:
- Around line 186-192: The search-clause logic in getSessionList and
getSessionsCount is duplicated, so extract it into a shared helper (following
the getXSearchWhereClause pattern used in profile.service.ts) and have both call
sites use it. While doing so, also fix the LIKE wildcard handling by escaping
user input for both the profile_id prefix match and the ILIKE path/referrer
search so characters like _ do not act as wildcards. Use the existing
looksLikeProfileId branch and the profile_id/search field names to locate the
shared logic.
🪄 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 Plus

Run ID: 67b6dede-2263-4d01-9b73-f832181c6641

📥 Commits

Reviewing files that changed from the base of the PR and between 073ca8a and 3fb4a12.

📒 Files selected for processing (2)
  • packages/db/src/services/profile.service.ts
  • packages/db/src/services/session.service.ts

Comment on lines +157 to +174
// Single-token identifiers (firebase uids, uuids, cuids): no spaces or "@",
// 16+ chars. ID-shaped searches resolve via the (project_id, id) primary key
// instead of ILIKE-scanning email/name columns, which times out on projects
// with tens of millions of profiles.
export function looksLikeProfileId(search: string) {
return /^[A-Za-z0-9_.:-]{16,}$/.test(search);
}

function getProfileSearchWhereClause(search: string) {
if (looksLikeProfileId(search)) {
// Prefix LIKE (no leading wildcard) is primary-key-prunable and also
// covers exact matches. The id regex excludes LIKE metacharacters.
return `id LIKE ${sqlstring.escape(`${search}%`)}`;
}
const pattern = sqlstring.escape(`%${search}%`);
return `(email ILIKE ${pattern} OR first_name ILIKE ${pattern} OR last_name ILIKE ${pattern})`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant code and inspect the surrounding implementations.
git ls-files packages/db/src/services/profile.service.ts packages/db/src/services/session.service.ts \
  | xargs -r -I{} sh -c 'echo "===== {} ====="; nl -ba "{}" | sed -n "130,220p"'

# Find the helper/library used for escaping.
rg -n "sqlstring\.escape|looksLikeProfileId|getProfileSearchWhereClause|profile_id|ILIKE|LIKE" packages/db/src/services -S

Repository: Dashverse/openpanel-forked

Length of output: 47088


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact search-building code and nearby usages.
for f in packages/db/src/services/profile.service.ts packages/db/src/services/session.service.ts; do
  echo "===== $f ====="
  nl -ba "$f" | sed -n '130,230p'
done

echo "===== search for escaping / LIKE usage ====="
rg -n "sqlstring\.escape|looksLikeProfileId|getProfileSearchWhereClause|profile_id|ILIKE|LIKE" packages/db/src/services -S

Repository: Dashverse/openpanel-forked

Length of output: 260


Escape LIKE wildcards in ID searches. looksLikeProfileId allows _, but id LIKE ... treats it as a wildcard; sqlstring.escape does not escape LIKE metacharacters, so IDs such as user_12345 can match extra profiles. Apply the same fix in packages/db/src/services/session.service.ts.

🤖 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 `@packages/db/src/services/profile.service.ts` around lines 157 - 174, The
ID-search branch in getProfileSearchWhereClause treats `_` as a SQL LIKE
wildcard because sqlstring.escape only quotes the string and does not escape
LIKE metacharacters. Update the ID-prefix query in profile.service.ts to escape
LIKE wildcards before appending the trailing % so exact ID-shaped searches like
user_12345 do not overmatch, and apply the same fix to the analogous search
logic in session.service.ts.

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.

1 participant