feat(search): match profile id in profiles and sessions search - #355
feat(search): match profile id in profiles and sessions search#355muralikrishna13-dash wants to merge 2 commits into
Conversation
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>
📝 WalkthroughWalkthroughSearch 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. ChangesSearch Filter Updates
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/db/src/services/profile.service.ts (1)
205-206: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated escape+wildcard pattern logic.
The
sqlstring.escape(%${search}%)pattern-building line is repeated identically in both functions here, and again (twice) insession.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
📒 Files selected for processing (2)
packages/db/src/services/profile.service.tspackages/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>
|
Good catch — reworked in 3fb4a12. You're right that adding New approach: ID-shaped searches never touch the ILIKE path at all. A single-token query (16+ chars, no 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). |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/db/src/services/session.service.ts (1)
186-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate search-clause logic; extract a shared helper.
The id-shaped branching (
looksLikeProfileId→profile_id LIKEprefix vs. escapedILIKEover path/referrer fields) is copy-pasted identically betweengetSessionListandgetSessionsCount.profile.service.tsalready established the pattern of centralizing this into agetXSearchWhereClausehelper — 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) —_insearchwill act as a wildcard for bothprofile_idand 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
getSessionListandgetSessionsCount: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
📒 Files selected for processing (2)
packages/db/src/services/profile.service.tspackages/db/src/services/session.service.ts
| // 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})`; | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 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 -SRepository: 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 -SRepository: 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.
Problem
Profile search only matches
email,first_name, andlast_name. Our users are identified bybackend_useridand 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
getProfileList/getProfileListCount): search now also matchesid(partial match works, e.g. a userid prefix).getSessionList/getSessionsCount): search now also matchesprofile_id.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